Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 57 additions & 19 deletions .github/workflows/release.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -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
Expand All@@ -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}"
Expand Down
65 changes: 0 additions & 65 deletions .github/workflows/release_config.json

This file was deleted.

8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -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
Expand Down
7 changes: 7 additions & 0 deletions pyproject.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -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"]

Expand Down
22 changes: 0 additions & 22 deletions tests/analysis/java/test_jcodeanalyzer.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,7 +19,6 @@
"""

import os
import sys
import json
from typing import Dict, List, Tuple
from unittest.mock import patch, MagicMock
Expand DownExpand Up@@ -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"""

Expand Down
4 changes: 2 additions & 2 deletions tests/conftest.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -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, <jar>]``).
"""
return None

Expand Down