Skip to content

Java backend: depend on the codeanalyzer-java wheel (bundled JVM) instead of a bundled jar plus _jdk.py #339

Closed

Description

@rahlk

Problem

The Java backend carries its analyzer two ways that the other backends do not: the jar is checked in and force-included under cldk/analysis/java/codeanalyzer/jar/ (release.yml injects another copy at build time, #336), and cldk/analysis/java/codeanalyzer/_jdk.py downloads a pinned Temurin JDK with jmods/ into <cache_dir>/java/jdk/ on first use, exporting JAVA_HOME because the analyzer's WALA scope read $JAVA_HOME/jmods. That is ~35 MB in every cldk wheel and a ~200 MB download per project cache, plus JDK-resolution code the SDK has to maintain.

codeanalyzer-java 3.0.2 removes the reason for all of it (codeanalyzer-java#222): the analyzer reads its primordial scope from the running JVM through jrt:/, so no JAVA_HOME and no jmods/ are needed, and it ships on PyPI as codeanalyzer-java, a pure wheel with the jar under _bin/ and jdk4py (Temurin 21) as the runtime. The package exposes jar_path(), java_path() and command(*args), the same seam codeanalyzer-typescript.bin_path() gives the TypeScript backend.

Scope boundary

In scope: pyproject.toml depends on codeanalyzer-java==<pin> next to the other two backends, and the pin in [tool.backend-versions] becomes real; JCodeanalyzer._get_codeanalyzer_exec returns codeanalyzer_java.command(); delete _jdk.py, _locate_jar, the checked-in jar, the [tool.hatch.build] artifacts force-include, and the jar inject/verify steps in release.yml; tests that monkeypatch ensure_jdk follow.

Out of scope: the Java model migration to schema v2 (#310) — but it is a prerequisite, because the only current wheel is 3.0.2 (v2 output; the 2.3.7 on PyPI is a stale native-image build). Land this on top of leg 3, or in the same PR if the pin bump is small.

Goals

  • pip install cldk on a machine with no Java runs CLDK.java(...) at AnalysisLevel.call_graph
  • No JAVA_HOME is read or set by the SDK; no JDK is downloaded; no jar is checked in or injected
  • The codeanalyzer-java pin is the single source of the analyzer version, and release.yml no longer fetches from releases/latest (closes the mechanism release.yml ignores the codeanalyzer-java pin and bundles releases/latest #336 patches)

Caveats and known risks

  • codeanalyzer-java pins jdk4py>=21,<22: WALA 1.6.x's Shrike rejects class files above version 67, and jdk4py 25 ships version 69. The SDK inherits that pin transitively; nothing to do unless an SDK dependency ever wants jdk4py 25.
  • jdk4py carries a GPLv2 license classifier (Temurin's GPLv2 + Classpath Exception, like every JDK). License scanners on downstream Apache-2.0 trees may flag it; document it, do not work around it.
  • Windows: codeanalyzer_java.__main__ falls back to subprocess.call there; the SDK already spawns via subprocess, so no change, but it is untested on Windows.
  • cache_dir no longer hosts a JDK. Single-file source mode (fix(java): source-code analysis crashes with Path(None) — JDK cache resolution assumes a project dir #256) loses the "cannot host a JDK" failure and should now just work; confirm rather than assume.

Definition of done

  • Fresh venv with no Java on PATH and JAVA_HOME unset: CLDK.java(project_path=<daytrader8>, analysis_level=AnalysisLevel.call_graph).get_call_graph() returns the same edge set the previous JDK-download path returned (1876 edges on daytrader8 at analyzer 3.0.2; 1391 declared+rta, 375 rta, 110 declared)
  • grep -r 'JAVA_HOME\|ensure_jdk\|jmods' cldk is empty; cldk/analysis/java/codeanalyzer/jar/ and _jdk.py are gone
  • Built wheel contains no .jar; unzip -l dist/*.whl | grep -c '\.jar$' is 0
  • Java test suite green with the ensure_jdk monkeypatch removed

Activity

  1. rahlk commented on Sep 6, 2026

    @rahlk
    CollaboratorAuthor

    Measured the definition-of-done numbers before wiring this up, and the packaging change is provably neutral — but the absolute edge counts in the DoD do not match this repo's own fixture.

    All three runs below use the sources in tests/resources/java/application/daytrader8-1.2.zip (138 .java files), at -a 4:

    run edges declared+rta rta declared
    3.0.1 jar on the _jdk.py-provisioned Temurin 21.0.5+11 1862 1378 375 109
    3.0.2 wheel via codeanalyzer_java.command(), --no-build, JAVA_HOME unset 1862 1378 375 109
    3.0.2 wheel via command(), with the Maven auto-build (Maven 3.9.16 present) 1862 1378 375 109

    So the old JDK-download path and the wheel return the identical edge set, and the build/no-build choice makes no difference on this project. The DoD's 1876 / 1391 / 375 / 110 is 14 edges higher; the likely cause is a different daytrader8 tree, since tests/conftest.py downloads daytrader8 from GitHub when the zip is absent.

    Suggest the DoD assert equality between the two paths on the same sources rather than a literal count, which is the property that actually matters here and does not drift with the corpus.

    Two more things confirmed while checking:

    • 3.0.2 is a packaging change only. The analysis.json key sets are identical to 3.0.1 at envelope, application, module, type, callable and body-node level, and schema.neo4j.json is byte-identical (contract 2.0.0, same labels and relationship types). Models written against 3.0.1 need no change.
    • codeanalyzer_java exposes command(*args) -> List[str], jar_path(), java_path(), __version__ (3.0.2); command() returns [<jdk4py java>, -jar, <wheel jar>, *args] and the run needs no JAVA_HOME. The wheel is 35 MB plus jdk4py (Temurin 21.0.8).

    On the license caveat: jdk4py's PyPI classifier says plain GPLv2, but the shipped runtime carries GPLv2 with the Classpath Exception (java-runtime/legal/java.base/LICENSE plus ASSEMBLY_EXCEPTION) — the ordinary OpenJDK terms, and the exception is what permits Apache-2.0 code to run on it. Nothing about this repo's licensing changes; the only new thing is that dependency tooling now sees a GPLv2-classified row where a runtime JDK download was invisible to it. Worth one sentence in the docs, as the issue says.

    This is folded into leg 3a (#310) as its own task, so no 35 MB jar enters git history on the way.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions