From f32c62d16800b4cac0d67119c9ad267a81b1a5ee Mon Sep 17 00:00:00 2001 From: Jeremie Grodziski Date: Thu, 5 Nov 2026 11:53:09 +0000 Subject: [PATCH] feat: bin wrappers for java and csharp build.sh builds the Java jar and the C# binary, but bin/ had no command for either, and docs/metrics.md already told clone users to run ./bin/codegraph-java. - bin/codegraph-java runs extractors/java/target/codegraph-java.jar on the first Java at or above pom.xml's maven.compiler.release, searched in JAVA_HOME, on PATH, under ~/.sdkman, then macOS's java_home. - bin/codegraph-csharp runs extractors/csharp/dist//, with the .exe fallback the ruby and metrics wrappers use. - test.sh: a new gate runs the jar through bin/codegraph-java against fixtures/java (same root rewrite as the native gate); the published C# gate now runs through bin/codegraph-csharp, like elixir and ruby. - ci.yml csharp-smoke downloaded its artifact into bin/, which would overwrite the committed wrapper; it now lands in extractors/csharp/dist//, where build.sh publishes it. - CLAUDE.md, build.sh header, extractor READMEs and docs, and the site's install and reference pages name the two commands. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01PKCVh1pKrt8Z17Ln1pEEkq --- .github/workflows/ci.yml | 11 ++-- CLAUDE.md | 3 ++ bin/codegraph-csharp | 32 ++++++++++++ bin/codegraph-java | 50 +++++++++++++++++++ build.sh | 5 +- docs/cli.md | 3 ++ docs/csharp-extractor.md | 3 ++ extractors/java/README.md | 3 ++ test.sh | 22 +++++--- website/content/docs/how-to/install.md | 11 ++-- .../docs/reference/csharp-extractor.md | 1 + .../content/docs/reference/java-extractor.md | 2 + 12 files changed, 129 insertions(+), 17 deletions(-) create mode 100755 bin/codegraph-csharp create mode 100755 bin/codegraph-java diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 50741c6..34482e4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -334,16 +334,19 @@ jobs: - uses: actions/download-artifact@v4 with: name: codegraph-csharp-${{ matrix.rid }} - path: bin + # Where build.sh publishes it — never bin/, whose bin/codegraph-csharp + # is the committed wrapper the download would overwrite. + path: extractors/csharp/dist/${{ matrix.rid }} # Nothing is installed on this runner for the extractor: the binary # carries the runtime, Roslyn and the BCL. Run from the repo root with # the RELATIVE --src the snapshot was made with (the typed path is the # header's `root`). - shell: bash run: | - chmod +x "bin/${{ matrix.exe }}" - "bin/${{ matrix.exe }}" --version - "bin/${{ matrix.exe }}" --src fixtures/csharp/src --out out.jsonl --progress plain + bin="extractors/csharp/dist/${{ matrix.rid }}/${{ matrix.exe }}" + chmod +x "$bin" + "$bin" --version + "$bin" --src fixtures/csharp/src --out out.jsonl --progress plain cmp out.jsonl fixtures/csharp/expected/model.jsonl echo "byte-identical on ${{ matrix.os }}" diff --git a/CLAUDE.md b/CLAUDE.md index a48d1f0..58c6ead 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -174,6 +174,8 @@ cd extractors/java && ./mvnw package # Maven Wrapper — `mvn` is NOT install java -jar target/codegraph-java.jar --src --out model.jsonl # the jar needs a JDK on PATH; non-interactive shells do not source sdkman: # export JAVA_HOME="$HOME/.sdkman/candidates/java/25.0.4-tem" +./bin/codegraph-java --src --out model.jsonl # the jar, from the repo root, on the first Java 17+ +# in JAVA_HOME, on PATH or under ~/.sdkman — no export needed ./build.sh --java --native # GraalVM binary for THIS platform only — # native-image cannot cross-compile, so CI # builds one RID per runner @@ -185,6 +187,7 @@ cd extractors/csharp && dotnet test -c Release # needs the .NET 10 SDK (see ex ./build.sh --csharp --publish-all # linux-x64/arm64, osx-x64/arm64, win-x64 — all from this host ./test.sh --csharp # dotnet test + the published binary must reproduce the snapshot extractors/csharp/dist/linux-x64/codegraph-csharp --src --out model.jsonl +./bin/codegraph-csharp --src --out model.jsonl # the same, for THIS host's RID # runtime-only or SDK forms (dotnet , dotnet run): docs/csharp-extractor.md ./build.sh --ts --sea # ONE Node single-executable of the CLI + daemon + both frontends diff --git a/bin/codegraph-csharp b/bin/codegraph-csharp new file mode 100755 index 0000000..330109b --- /dev/null +++ b/bin/codegraph-csharp @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +# The C# extractor as a program: `./bin/codegraph-csharp --src DIR --out model.jsonl`. +# +# Extensionless on purpose — the name IS the command. It runs the +# self-contained binary `build.sh --csharp` publishes for THIS host beside +# ITSELF (a git worktree or a moved checkout runs its own dist/); the binary +# carries its runtime, Roslyn and the BCL reference pack — no .NET installed. +set -euo pipefail +here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# The RID build.sh publishes by default (scripts/lib.sh host_rid). +case "$(uname -s)" in + Darwin) os="osx" ;; + MINGW*|MSYS*|CYGWIN*) os="win" ;; + *) os="linux" ;; +esac +case "$(uname -m)" in + arm64|aarch64) arch="arm64" ;; + *) arch="x64" ;; +esac +rid="$os-$arch" + +bin="$here/../extractors/csharp/dist/$rid/codegraph-csharp" +[ -x "$bin" ] || bin="$bin.exe" + +if [ ! -x "$bin" ]; then + printf 'codegraph-csharp: extractors/csharp/dist/%s/codegraph-csharp is missing\n' "$rid" >&2 + printf 'Run `./build.sh --csharp` in %s and try again.\n' "$(cd "$here/.." && pwd)" >&2 + exit 1 +fi + +exec "$bin" "$@" diff --git a/bin/codegraph-java b/bin/codegraph-java new file mode 100755 index 0000000..ee53eda --- /dev/null +++ b/bin/codegraph-java @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +# The Java extractor as a program: `./bin/codegraph-java --src DIR --out model.jsonl`. +# +# Extensionless on purpose — the name IS the command. It runs the shaded jar +# `build.sh --java` produces beside ITSELF (a git worktree or a moved checkout +# runs its own target/), on the first Java at or above pom.xml's +# maven.compiler.release — a JRE will do, ECJ only needs its class library. +# Non-interactive shells never source sdkman, so its candidates are probed too. +set -euo pipefail +here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +jar="$here/../extractors/java/target/codegraph-java.jar" + +if [ ! -f "$jar" ]; then + printf 'codegraph-java: extractors/java/target/codegraph-java.jar is missing\n' >&2 + printf 'Run `./build.sh --java` in %s and try again.\n' "$(cd "$here/.." && pwd)" >&2 + exit 1 +fi + +want="$(sed -n 's:.*\([0-9][0-9]*\).*:\1:p' \ + "$here/../extractors/java/pom.xml" | head -1)" + +# "1.8.0_392" -> 8, "17.0.2" -> 17, "21" -> 21 (a GA build prints no minor). +major() { + "$1" -version 2>&1 | sed -n '1s/.*version "\([^"]*\)".*/\1/p' | awk -F'[.+_-]' '{ print ($1 == 1 ? $2 : $1) }' +} + +# Search order is preference order: JAVA_HOME, PATH, sdkman's selected JDK, +# every sdkman candidate, then macOS's registry. The first one recent enough wins. +find_java() { + local candidate have + set -- "${JAVA_HOME:+$JAVA_HOME/bin/java}" "$(command -v java 2>/dev/null || true)" \ + "$HOME/.sdkman/candidates/java/current/bin/java" "$HOME"/.sdkman/candidates/java/*/bin/java + if [ -x /usr/libexec/java_home ]; then + set -- "$@" "$(/usr/libexec/java_home -v "$want+" 2>/dev/null || true)/bin/java" + fi + for candidate in "$@"; do + [ -n "$candidate" ] && [ -x "$candidate" ] || continue + have="$(major "$candidate")" + if [ -n "$have" ] && [ "$have" -ge "$want" ] 2>/dev/null; then printf '%s' "$candidate"; return 0; fi + done + return 1 +} + +if ! java="$(find_java)"; then + printf 'codegraph-java: no Java %s+ in JAVA_HOME, on PATH or under ~/.sdkman — the jar needs one\n' "$want" >&2 + printf 'sdkman: sdk install java 25.0.4-tem homebrew: brew install --cask temurin\n' >&2 + exit 1 +fi + +exec "$java" -jar "$jar" "$@" diff --git a/build.sh b/build.sh index ebe11ea..957ae96 100755 --- a/build.sh +++ b/build.sh @@ -5,7 +5,7 @@ # TypeScript : pnpm -r build -> packages/*/dist (incl. the viz bundle # `codegraph city --serve` looks for) and # extractors/typescript/dist/cli.js (bin/codegraph-typescript) -# Java : ./mvnw package -> extractors/java/target/codegraph-java.jar +# Java : ./mvnw package -> extractors/java/target/codegraph-java.jar (bin/codegraph-java) # --native -> extractors/java/dist//codegraph-java # (GraalVM native image; HOST platform only — # native-image drives the host linker and @@ -13,7 +13,8 @@ # is one CI job per runner, not one job) # C# : dotnet publish -> extractors/csharp/dist//codegraph-csharp # (self-contained single file; --publish-all -# builds the five-RID matrix from this host) +# builds the five-RID matrix from this host; +# bin/codegraph-csharp runs the host's) # Elixir mix escript.build -> extractors/elixir/dist/codegraph-elixir # (an escript: Elixir embedded, needs Erlang/OTP # on the machine that runs it — bin/codegraph-elixir) diff --git a/docs/cli.md b/docs/cli.md index 9cc6825..855790c 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -440,6 +440,9 @@ java -jar target/codegraph-java.jar # defaults: the current directory, # into -codegraph.jsonl ``` +From the repo root, `./bin/codegraph-java` runs the same jar with the same +flags, on the first Java 17+ in `JAVA_HOME`, on `PATH` or under `~/.sdkman`. + Spoon runs in noClasspath mode, so the sources need not compile and no dependency jars are needed. What cannot be resolved becomes a stub with its edges kept, so the picture is honest about its gaps. `--src` is repeatable: diff --git a/docs/csharp-extractor.md b/docs/csharp-extractor.md index 955b19d..d122b35 100644 --- a/docs/csharp-extractor.md +++ b/docs/csharp-extractor.md @@ -47,6 +47,9 @@ xattr -d com.apple.quarantine codegraph-csharp codegraph-csharp.exe --src path\to\src --out model.jsonl ``` +From a clone, `./bin/codegraph-csharp` runs the binary published for the +host's platform, with the same flags. + No `libicu` is needed on Linux: the extractor is built with invariant globalization, so it runs on a minimal container image. diff --git a/extractors/java/README.md b/extractors/java/README.md index c612a06..f818fc4 100644 --- a/extractors/java/README.md +++ b/extractors/java/README.md @@ -64,6 +64,9 @@ Go or .NET extractor would use. ```bash java -jar target/codegraph-java.jar --src src/main/java --out model.jsonl +# the same jar from the repo root, on the first Java 17+ in JAVA_HOME, on PATH or under ~/.sdkman +./bin/codegraph-java --src src/main/java --out model.jsonl + # both options default: extract the current directory into -codegraph.jsonl java -jar target/codegraph-java.jar ``` diff --git a/test.sh b/test.sh index c0b9915..1153b12 100755 --- a/test.sh +++ b/test.sh @@ -7,11 +7,12 @@ # unit pnpm -r test (vitest + fast-check property suites) # schemas pnpm run gen:schemas must be a NO-OP — schemas/ is a committed # artifact and the cross-language contract; drift there is a bug -# java ./mvnw test, then the NATIVE binary (if build.sh made one) -# re-extracts fixtures/java/src and must reproduce the committed -# snapshot — the only test that sees the image's own code path: no -# java.home, no VM class library, the embedded ct.sym reference -# csharp dotnet test, then the PUBLISHED binary (if build.sh made one) +# java ./mvnw test, then the built JAR (bin/codegraph-java) and the +# NATIVE binary (each if build.sh made one) re-extract +# fixtures/java/src and must reproduce the committed snapshot — the +# native one is the only test that sees the image's own code path: +# no java.home, no VM class library, the embedded ct.sym reference +# csharp dotnet test, then the PUBLISHED binary (bin/codegraph-csharp, if build.sh made one) # re-extracts fixtures/csharp/src and must reproduce the committed # snapshot byte for byte — the only test that sees the single-file # code path (embedded BCL, no Assembly.Location) @@ -225,6 +226,15 @@ if wants_java && [ "$SKIP_JAVA" = "no" ]; then phase "java extractor tests (./mvnw test)" \ sh -c "cd '$JAVA_DIR' && ./mvnw -B test" + # The shaded jar, not the test classpath, must reproduce the snapshot: it is + # what `./bin/codegraph-java` runs from a clone. Same `root` rewrite as below. + if [ -f "$JAVA_DIR/target/codegraph-java.jar" ]; then + phase "built java jar reproduces the snapshot" \ + sh -c "cd '$ROOT' && out=\$(mktemp) && ./bin/codegraph-java --src fixtures/java/src --out \"\$out\" --progress none >/dev/null 2>&1 && head -1 \"\$out\" | grep -qE '\"root\":\"[^\"]*fixtures[^\"]*java[^\"]*src\"' && sed '1s|\"root\":\"[^\"]*\"|\"root\":\"fixtures/java/src\"|' \"\$out\" | cmp - fixtures/java/expected/model.jsonl; rc=\$?; rm -f \"\$out\"; exit \$rc" + else + warn "no jar at $JAVA_DIR/target/codegraph-java.jar — run ./build.sh --java for the smoke test" + fi + # The same acceptance check the C# binary gets, and for the same reason: a # native image is a different runtime, not a repackaging. It has no java.home, # so ECJ finds no platform library and falls back to the ct.sym reference the @@ -263,7 +273,7 @@ if wants_csharp && [ "$SKIP_CSHARP" = "no" ]; then [ -x "$bin" ] || bin="$bin.exe" if [ -x "$bin" ]; then phase "published csharp binary reproduces the snapshot" \ - sh -c "cd '$ROOT' && out=\$(mktemp) && '$bin' --src fixtures/csharp/src --out \"\$out\" --progress none >/dev/null 2>&1 && cmp \"\$out\" fixtures/csharp/expected/model.jsonl; rc=\$?; rm -f \"\$out\"; exit \$rc" + sh -c "cd '$ROOT' && out=\$(mktemp) && ./bin/codegraph-csharp --src fixtures/csharp/src --out \"\$out\" --progress none >/dev/null 2>&1 && cmp \"\$out\" fixtures/csharp/expected/model.jsonl; rc=\$?; rm -f \"\$out\"; exit \$rc" else warn "no published binary at $CSHARP_DIR/dist/$(host_rid) — run ./build.sh --csharp for the smoke test" fi diff --git a/website/content/docs/how-to/install.md b/website/content/docs/how-to/install.md index e908864..96ae117 100644 --- a/website/content/docs/how-to/install.md +++ b/website/content/docs/how-to/install.md @@ -152,16 +152,17 @@ for Elixir, Rust 1.94+ and libclang for Ruby, Rust 1.94+ for the metrics pass. ```bash export JAVA_HOME="$HOME/.sdkman/candidates/java/25.0.4-tem" export PATH="$JAVA_HOME/bin:$PATH" - (cd extractors/java && ./mvnw -B package) # → extractors/java/target/codegraph-java.jar; `mvn` is not needed - ./build.sh --csharp # → extractors/csharp/dist//codegraph-csharp + (cd extractors/java && ./mvnw -B package) # → bin/codegraph-java (the jar); `mvn` is not needed + ./build.sh --csharp # → bin/codegraph-csharp ./build.sh --elixir # → bin/codegraph-elixir ./build.sh --ruby # → bin/codegraph-ruby (Rust 1.94+ and libclang to build) ./build.sh --metrics # → bin/codegraph-metrics (Rust 1.94+ to build) ``` - `bin/codegraph-typescript` is already built by step 1. The jar runs as - `java -jar extractors/java/target/codegraph-java.jar`; every page of this - documentation writes `codegraph-java`, and the two take the same flags. + `bin/codegraph-typescript` is already built by step 1. `bin/codegraph-java` + runs the jar on the first Java 17+ in `JAVA_HOME`, on `PATH` or under + `~/.sdkman`; every page of this documentation writes `codegraph-java`, and + the two take the same flags. ## Using it at work diff --git a/website/content/docs/reference/csharp-extractor.md b/website/content/docs/reference/csharp-extractor.md index d5c86c4..dcdea1c 100644 --- a/website/content/docs/reference/csharp-extractor.md +++ b/website/content/docs/reference/csharp-extractor.md @@ -14,6 +14,7 @@ codegraph-csharp --src --out model.jsonl ./build.sh --csharp # publish the host's binary → extractors/csharp/dist//codegraph-csharp ./build.sh --csharp --publish-all # linux-x64, linux-arm64, osx-x64, osx-arm64, win-x64 — all from one host extractors/csharp/dist/osx-arm64/codegraph-csharp --src --out model.jsonl +./bin/codegraph-csharp --src --out model.jsonl # the same, for the host's platform ``` The binary needs nothing installed; building it needs the .NET 10 SDK (see `extractors/csharp/README.md`). diff --git a/website/content/docs/reference/java-extractor.md b/website/content/docs/reference/java-extractor.md index bbf84d4..ec2a096 100644 --- a/website/content/docs/reference/java-extractor.md +++ b/website/content/docs/reference/java-extractor.md @@ -22,6 +22,8 @@ cd extractors/java && ./mvnw -B package # `mvn` is not installed; use the wr java -jar target/codegraph-java.jar --src --out model.jsonl ``` +From the repo root, `./bin/codegraph-java` runs the same jar without the exports: it takes the first Java 17+ in `JAVA_HOME`, on `PATH` or under `~/.sdkman`. + ## Synopsis ```