From 548a97d9b96cac72c4d5280766236fd68cf6431a Mon Sep 17 00:00:00 2001 From: Rahul Krishna Date: Wed, 22 Jul 2026 17:52:05 -0400 Subject: [PATCH] fix: backport JAR-bundling and release-workflow fixes to 2.0 (#284, #289, #290, #292) Ports the fixes shipped on main to the 2.0 line (2.0.0-rc.1 shipped jarless): - Force-include the codeanalyzer-java JAR via [tool.hatch.build] artifacts (#284) - Pipe-safe release guard that fails a jarless build before publishing (#284) - Source release notes from CHANGELOG.md and fail on blank; drop the label-based changelog scraper and orphaned release_config.json (#289) - Remove the obsolete native-binary test and its unused import (#290) - Fix the stale conftest docstring for the retired native binary (#292) Verified: build on this branch (with .git) yields a 32MB wheel+sdist containing the JAR; tests/analysis/java/test_jcodeanalyzer.py -> 42 passed. --- .github/workflows/release.yml | 76 +++++++++++++++++------ .github/workflows/release_config.json | 65 ------------------- CHANGELOG.md | 8 +++ pyproject.toml | 7 +++ tests/analysis/java/test_jcodeanalyzer.py | 22 ------- tests/conftest.py | 4 +- 6 files changed, 74 insertions(+), 108 deletions(-) delete mode 100644 .github/workflows/release_config.json diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ad75552a..47681606 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -65,28 +65,66 @@ jobs: - name: Build Package run: uv build - - name: Read Changelog Entry - id: changelog_reader - uses: mindsers/changelog-reader-action@v2 - with: - validation_level: warn - version: ${{ steps.tag_name.outputs.current_version }} - path: ./CHANGELOG.md - - - name: Build Changelog - id: gen_changelog - uses: mikepenz/release-changelog-builder-action@v5 - with: - failOnError: "true" - configuration: .github/workflows/release_config.json - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Verify the codeanalyzer JAR is bundled + # Guard against the hatchling/.gitignore regression (issue #284): a jarless wheel + # installs fine but fails at runtime with "codeanalyzer jar not found". Fail the + # release here rather than publish a broken artifact to PyPI. + # + # The listing is captured before grepping: piping `tar tzf` (which decompresses the + # whole 32MB sdist) straight into `grep -q` lets grep close the pipe on first match, + # SIGPIPE-killing tar and — under `pipefail` — reporting a false "missing JAR". + run: | + set -euo pipefail + jar_re='codeanalyzer/jar/codeanalyzer-[0-9][^/]*\.jar$' + fail=0 + for f in dist/*.whl dist/*.tar.gz; do + case "$f" in + *.whl) listing=$(unzip -l "$f") ;; + *.tar.gz) listing=$(tar tzf "$f") ;; + esac + if grep -qE "$jar_re" <<<"$listing"; then + echo " ✓ $f" + else + echo "::error::$f is missing the codeanalyzer JAR" + grep -i '\.jar' <<<"$listing" || echo " (no .jar entries at all)" + fail=1 + fi + done + if [ "$fail" -ne 0 ]; then + echo "Refusing to publish a jarless release."; exit 1 + fi + echo "codeanalyzer JAR present in wheel and sdist ✓" + + - name: Extract release notes from CHANGELOG.md + id: notes + # Source the release body from the hand-written CHANGELOG.md section for this tag — + # deterministic and independent of PR labels — and refuse to publish/announce a blank + # body. The previous label-based changelog scraper emitted nothing for unlabeled PRs, + # which blanked the release and crashed the org announcement. See issue #289. + run: | + set -euo pipefail + version="${GITHUB_REF#refs/tags/}" # e.g. v1.4.4 — matches the "## [v1.4.4]" heading + notes=$(awk -v h="## [$version]" ' + !seen && index($0, h) == 1 { seen = 1; next } + seen && index($0, "## [") == 1 { exit } + seen { print } + ' CHANGELOG.md | sed '/./,$!d' | tac | sed '/./,$!d' | tac) # strip blank edges + if [ -z "$notes" ]; then + echo "::error::No CHANGELOG.md entry for $version — refusing to publish a blank release." + exit 1 + fi + { + echo "notes<<__CHANGELOG_EOF__" + echo "$notes" + echo "__CHANGELOG_EOF__" + } >> "$GITHUB_OUTPUT" + echo "Release notes for $version:"; echo "$notes" - name: Publish Release on GitHub uses: softprops/action-gh-release@v2 with: files: dist/* - body: ${{ steps.gen_changelog.outputs.changelog }} + body: ${{ steps.notes.outputs.notes }} # Auto-open a repo-level Discussion linked to this release, seeded with # the same notes. Requires Discussions enabled and this category to exist. discussion_category_name: Announcements @@ -96,13 +134,13 @@ jobs: # Mirror the release announcement into the ORG-level discussions, which are # backed by codellm-devkit/.github. GITHUB_TOKEN can't write cross-repo, so # this uses a PAT (ORG_DISCUSSIONS_TOKEN) with repo scope, and posts via the - # createDiscussion GraphQL mutation. The body (the generated changelog) is + # createDiscussion GraphQL mutation. The body (the CHANGELOG.md notes) is # passed via env to avoid shell-injection, matching the repo-level post. - name: Announce in org-level discussions (codellm-devkit/.github) continue-on-error: true # a failed org post must not fail an otherwise-good release env: GH_TOKEN: ${{ secrets.ORG_DISCUSSIONS_TOKEN }} - BODY: ${{ steps.gen_changelog.outputs.changelog }} + BODY: ${{ steps.notes.outputs.notes }} run: | set -uo pipefail VERSION="${GITHUB_REF#refs/tags/v}" diff --git a/.github/workflows/release_config.json b/.github/workflows/release_config.json deleted file mode 100644 index 200120c7..00000000 --- a/.github/workflows/release_config.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "categories": [ - { - "title": "## ✨ Release", - "labels": [ - "release" - ] - }, - { - "title": "## 🚀 Features", - "labels": [ - "kind/feature", - "enhancement" - ] - }, - { - "title": "## 🐛 Fixes", - "labels": [ - "fix", - "bug" - ] - }, - { - "title": "## ♻️ Refactoring", - "labels": [ - "refactoring" - ] - }, - { - "title": "## ⚡️ Performance Improvements", - "labels": [ - "performance" - ] - }, - { - "title": "## \uD83D\uDCDA Documentation", - "labels": [ - "documentation", - "doc" - ] - }, - { - "title": "## \uD83D\uDEA6 Tests", - "labels": [ - "test" - ] - }, - { - "title": "## \uD83D\uDEE0 Other Updates", - "labels": [ - "other", - "kind/dependency-change" - ] - }, - { - "title": "## 🚨 Breaking Changes", - "labels": [ - "breaking" - ] - } - ], - "ignore_labels": [ - "ignore" - ] -} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 62f2020c..b7a89070 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed +- **Published wheels bundle the `codeanalyzer-java` JAR again.** `2.0.0-rc.1` (like the 1.2.0–1.4.3 + line) shipped without the bundled JAR, so `CLDK.java(...)` after a plain `pip install` raised + `CodeanalyzerExecutionException: codeanalyzer jar not found`. Hatchling applied the root + `.gitignore` `*.jar` rule at build time but not the nested `!codeanalyzer-*.jar` negation that + keeps the JAR tracked in git; a `[tool.hatch.build] artifacts` rule force-includes it, and the + release workflow now fails fast if a built artifact is missing the JAR. (#284) + ## [v2.0.0-rc.1] - 2026-07-16 First release candidate for 2.0.0 — the schema-v2 release. Both the TypeScript and Python diff --git a/pyproject.toml b/pyproject.toml index 1c139f83..8296fa7e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -74,6 +74,13 @@ test = [ requires = ["hatchling"] build-backend = "hatchling.build" +# The codeanalyzer-java JAR is force-included here: it lives under a `*.jar` .gitignore +# (re-included for git via a nested `!codeanalyzer-*.jar`), but hatchling honors the root +# ignore and not the nested negation, so without this it is silently dropped from every +# wheel/sdist built inside a git repo (i.e. in CI). See issue #284. +[tool.hatch.build] +artifacts = ["cldk/analysis/java/codeanalyzer/jar/*.jar"] + [tool.hatch.build.targets.wheel] packages = ["cldk"] diff --git a/tests/analysis/java/test_jcodeanalyzer.py b/tests/analysis/java/test_jcodeanalyzer.py index f674c30e..bec79314 100644 --- a/tests/analysis/java/test_jcodeanalyzer.py +++ b/tests/analysis/java/test_jcodeanalyzer.py @@ -19,7 +19,6 @@ """ import os -import sys import json from typing import Dict, List, Tuple from unittest.mock import patch, MagicMock @@ -197,27 +196,6 @@ def test_init_codeanalyzer_reuses_legacy_cache_when_compatible(test_fixture, cod assert compilation_unit.import_declarations[0].is_wildcard is False -def test_get_codeanalyzer_exec(test_fixture, analysis_json, tmp_path): - """Should resolve the codeanalyzer native binary command (packaged binary only).""" - - # Patch subprocess so that it does not run codeanalyzer - with patch("cldk.analysis.java.codeanalyzer.codeanalyzer.subprocess.run") as run_mock: - run_mock.return_value = MagicMock(stdout=analysis_json, returncode=0) - - code_analyzer = JCodeanalyzer( - project_dir=test_fixture, - source_code=None, - analysis_json_path=None, - analysis_level=AnalysisLevel.symbol_table, - eager_analysis=False, - target_files=None, - ) - - # The PyPI native binary, invoked via `python -m codeanalyzer_java`. There is no longer a - # backend-path override (the binary ships with the packaged dependency). - assert code_analyzer._get_codeanalyzer_exec() == [sys.executable, "-m", "codeanalyzer_java"] - - def test_generate_call_graph(test_fixture, analysis_json): """Should generate a graph""" diff --git a/tests/conftest.py b/tests/conftest.py index 5f3c0f1f..18837128 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -66,8 +66,8 @@ def analysis_json(analysis_json_fixture) -> str: def codeanalyzer_backend_path(): """Backend-path override for the Java analyzer in tests. - Returns None so the analyzer uses its default: the JVM-free native binary shipped in the - ``codeanalyzer-java`` PyPI package (``python -m codeanalyzer_java``). + Returns None so the analyzer uses its default: the ``codeanalyzer-*.jar`` bundled under + ``cldk/analysis/java/codeanalyzer/jar/``, run on a cached JDK (``[java, -jar, ]``). """ return None