diff --git a/.github/workflows/publish-addons.yml b/.github/workflows/publish-addons.yml index cb8a378b..1149da4f 100644 --- a/.github/workflows/publish-addons.yml +++ b/.github/workflows/publish-addons.yml @@ -51,41 +51,33 @@ jobs: - name: Check names and metadata run: uv run --directory tools/addons addons --root "$GITHUB_WORKSPACE" check - - name: Resolve the libs revision + - name: Fetch plugin-api-latest from CodeOnTheGo id: libs env: GH_TOKEN: ${{ github.token }} + COGO_REPO: appdevforall/CodeOnTheGo + COGO_RELEASE: plugin-api-latest run: | - # This workflow builds the addons itself instead of going through - # scripts/update-libs.sh, so nothing here knows which CodeOnTheGo commit - # produced libs/. "Update libs from CodeOnTheGo" records it in the subject - # of the commit it pushes, so the newest libs/ commit that names one wins. - # Ordinary commits touch libs/ too (a missing jar, a pipeline change), and - # keying on the single most recent one would let any of them block the whole - # release path until someone dispatched Update libs, which also bumps the - # toolchain as a side effect. Only "none of the recent ones names a sha" - # fails the run: these are the artifacts the gallery serves, and an empty - # export makes the builder omit the key and verify-provenance.sh skip its - # check, so the pairing would be lost on a green run. The subject is - # hand-writable, so the sha is validated rather than taken on trust, and - # capped at the 12 characters update-libs.sh records, so the two forms of - # one commit compare by prefix (subjects written before the --short=12 - # change carry 9). - subjects="$(gh api "repos/${GITHUB_REPOSITORY}/commits?path=libs/&sha=${GITHUB_SHA}&per_page=50" --jq '.[].commit.message | split("\n")[0]')" - revision="" - while IFS= read -r subject; do - candidate="${subject##*CodeOnTheGo@}" - if [[ "$subject" == *CodeOnTheGo@* && "$candidate" =~ ^[0-9a-f]{7,40}$ ]]; then - revision="${candidate:0:12}" - break - fi - done <<< "$subjects" - if [ -z "$revision" ]; then - echo "No recent commit touching libs/ names a CodeOnTheGo revision." >&2 - echo "One of them must carry 'CodeOnTheGo@' in its subject." >&2 + set -euo pipefail + target() { gh release view "$COGO_RELEASE" -R "$COGO_REPO" --json targetCommitish -q .targetCommitish; } + before="$(target)" + if ! [[ "$before" =~ ^[0-9a-f]{40}$ ]]; then + echo "$COGO_RELEASE targets '$before', not a commit sha." >&2 + exit 1 + fi + fetched="$(mktemp -d)" + gh release download "$COGO_RELEASE" -R "$COGO_REPO" -D "$fetched" \ + -p plugin-api.jar -p gradle-plugin.jar -p checksums.txt + ( cd "$fetched" && sha256sum --check --strict checksums.txt ) + after="$(target)" + if [ "$before" != "$after" ]; then + echo "$COGO_RELEASE was republished during the download ($before -> $after). Re-run." >&2 exit 1 fi + cp "$fetched/plugin-api.jar" "$fetched/gradle-plugin.jar" libs/ + revision="${before:0:12}" echo "revision=$revision" >> "$GITHUB_OUTPUT" + echo "Building against $COGO_REPO@$revision ($COGO_RELEASE)." >> "$GITHUB_STEP_SUMMARY" - name: Build, package, and stage id: stage diff --git a/CLAUDE.md b/CLAUDE.md index 02c74db7..7e6ff580 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -44,7 +44,7 @@ The script clones CoGo into `.cache/CodeOnTheGo/` on first run, rebuilds both ja There is also **one shared Gradle wrapper at the repo root** (`gradlew` + `gradle/wrapper/`). New plugins should use it — build them with `cd plugins/ && ../../gradlew assemblePlugin` rather than bundling a per-plugin `gradlew`/`gradle/wrapper/` copy. (`Flutter-Templates` and the five `AI-*` addons follow this; most older plugins still carry their own local wrapper and can be migrated opportunistically.) -An addon under `plugins/` references the shared jars as `../../libs/*.jar`. **Always use the repo-root `libs/` jars and the repo-root Gradle wrapper — never bundle per-plugin copies.** A plugin that ships its own `libs/plugin-api.jar` / `libs/gradle-plugin.jar` (e.g. copied from another plugin) can drift out of sync with the rest of the repo; point `build.gradle.kts` (`compileOnly`) and `settings.gradle.kts` (buildscript `classpath`) at `../../libs/*.jar` and delete any local `libs/`. The root `plugin-api.jar` already carries the full API surface (including `IdeTemplateService`/`CgtTemplateBuilder`), so newer sub-APIs do not justify a local copy. **A plugin folder is not standalone in isolation** — copy the root `libs/` along if you move one elsewhere. When CoGo's API changes, refresh via the script above or the **Update libs from CodeOnTheGo** GitHub Action (which commits the refreshed jars and cuts a release). Publishing addons is a separate workflow, **Publish addons**, which uploads to Cloudflare R2. +An addon under `plugins/` references the shared jars as `../../libs/*.jar`. **Always use the repo-root `libs/` jars and the repo-root Gradle wrapper — never bundle per-plugin copies.** A plugin that ships its own `libs/plugin-api.jar` / `libs/gradle-plugin.jar` (e.g. copied from another plugin) can drift out of sync with the rest of the repo; point `build.gradle.kts` (`compileOnly`) and `settings.gradle.kts` (buildscript `classpath`) at `../../libs/*.jar` and delete any local `libs/`. The root `plugin-api.jar` already carries the full API surface (including `IdeTemplateService`/`CgtTemplateBuilder`), so newer sub-APIs do not justify a local copy. **A plugin folder is not standalone in isolation** — copy the root `libs/` along if you move one elsewhere. When CoGo's API changes, refresh via the script above or the **Update libs from CodeOnTheGo** GitHub Action (which commits the refreshed jars and cuts a release). Publishing addons is a separate workflow, **Publish addons**, which uploads to Cloudflare R2. It does **not** build against the committed `libs/`: it overwrites both jars with the assets of CoGo's [`plugin-api-latest`](https://github.com/appdevforall/CodeOnTheGo/releases/tag/plugin-api-latest) release (cut by hand with CoGo's **Release plugin-api** workflow, from CoGo `main`) after checking them against its `checksums.txt`. So a plugin that adopts a new API publishes once that API is in a `plugin-api-latest` release, whether or not `libs/` has been refreshed. PR CI and local builds still use the committed `libs/`, which **Update libs** fills from CoGo `stage`, so an API that is on `stage` but not yet released passes PR CI and fails **Publish addons**. ### Credentials: use the host's `KeystoreSecretStore`, never your own crypto @@ -118,7 +118,7 @@ unzip -p /build/plugin/.cgp assets/cgp-build.properties `+dirty` has one systemic cause worth designing against: **a build-time download must land on a gitignored path.** A `downloadAssets` task that overwrites a git-tracked file (`ndk-installer` shipped a committed placeholder `ndk-cmake.tar.xz` until it was untracked) dirties the plugin directory on every build, so every artifact it ever produces records `+dirty` and no build of that plugin is traceable to a clean commit. Both download plugins now fetch onto ignored paths (`plugins/NDK-Installer/.gitignore`, `plugins/AI-Literacy-Course/.gitignore`); keep it that way when adding a new one. -`libs_revision` records which CoGo commit produced the jars the plugin was compiled against. The builder cannot see that checkout, so each build path exports `PLUGIN_LIBS_REVISION` first: `scripts/update-libs.sh` from the CodeOnTheGo checkout it just built, and **Publish addons** from the subject of the most recent commit touching `libs/` that names one (ordinary commits touch it too, so it scans back rather than reading only the newest). Compare two artifacts' `libs_revision` by prefix, not equality: subjects written before `--short=12` carry a 9-character sha. Note that under **Update libs from CodeOnTheGo** the plugin's own `revision` is the commit *before* the `chore: update libs` commit, because plugins are built before that commit is created; `libs_revision` is what pins the pairing. +`libs_revision` records which CoGo commit produced the jars the plugin was compiled against. The builder cannot see that checkout, so each build path exports `PLUGIN_LIBS_REVISION` first: `scripts/update-libs.sh` from the CodeOnTheGo checkout it just built, and **Publish addons** from the target commit of the `plugin-api-latest` release it downloaded. Compare two artifacts' `libs_revision` by prefix, not equality: `.cgp`s published before that change took theirs from a commit subject, and some of those carry a 9-character sha. Note that under **Update libs from CodeOnTheGo** the plugin's own `revision` is the commit *before* the `chore: update libs` commit, because plugins are built before that commit is created; `libs_revision` is what pins the pairing. `scripts/verify-provenance.sh` asserts the record after each `assemblePlugin`: a `.cgp` missing `assets/cgp-build.properties`, missing any required key, or disagreeing with the exported `PLUGIN_LIBS_REVISION` fails the run. `revision=unknown`, `+dirty` and `timestamp_source=wall-clock` warn instead — all three are legitimate off-CI (no `.git`, no `git` binary). All three workflows call it — **Build plugin artifacts** and **Update libs from CodeOnTheGo** through `scripts/update-libs.sh`, **Publish addons** directly, since it builds the addons itself. The last one is the one that matters most: those are the artifacts users install from the gallery. diff --git a/plugins/AI-Code-Suggestions/gradle.properties b/plugins/AI-Code-Suggestions/gradle.properties index 2c9f5454..9ba44411 100644 --- a/plugins/AI-Code-Suggestions/gradle.properties +++ b/plugins/AI-Code-Suggestions/gradle.properties @@ -1,3 +1,4 @@ android.useAndroidX=true android.nonTransitiveRClass=true kotlin.code.style=official +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 diff --git a/plugins/AI-Literacy-Course/gradle.properties b/plugins/AI-Literacy-Course/gradle.properties index 2c9f5454..9ba44411 100644 --- a/plugins/AI-Literacy-Course/gradle.properties +++ b/plugins/AI-Literacy-Course/gradle.properties @@ -1,3 +1,4 @@ android.useAndroidX=true android.nonTransitiveRClass=true kotlin.code.style=official +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 diff --git a/plugins/Icons-Repository/gradle.properties b/plugins/Icons-Repository/gradle.properties index 2c9f5454..9ba44411 100644 --- a/plugins/Icons-Repository/gradle.properties +++ b/plugins/Icons-Repository/gradle.properties @@ -1,3 +1,4 @@ android.useAndroidX=true android.nonTransitiveRClass=true kotlin.code.style=official +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 diff --git a/plugins/Rainbow-Brackets/gradle.properties b/plugins/Rainbow-Brackets/gradle.properties index 2c9f5454..9ba44411 100644 --- a/plugins/Rainbow-Brackets/gradle.properties +++ b/plugins/Rainbow-Brackets/gradle.properties @@ -1,3 +1,4 @@ android.useAndroidX=true android.nonTransitiveRClass=true kotlin.code.style=official +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 diff --git a/plugins/Random-XKCD/gradle.properties b/plugins/Random-XKCD/gradle.properties index 2c9f5454..9ba44411 100644 --- a/plugins/Random-XKCD/gradle.properties +++ b/plugins/Random-XKCD/gradle.properties @@ -1,3 +1,4 @@ android.useAndroidX=true android.nonTransitiveRClass=true kotlin.code.style=official +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 diff --git a/plugins/Vector-Search/gradle.properties b/plugins/Vector-Search/gradle.properties index 2c9f5454..9ba44411 100644 --- a/plugins/Vector-Search/gradle.properties +++ b/plugins/Vector-Search/gradle.properties @@ -1,3 +1,4 @@ android.useAndroidX=true android.nonTransitiveRClass=true kotlin.code.style=official +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 diff --git a/plugins/Voice-Alerts/gradle.properties b/plugins/Voice-Alerts/gradle.properties index 6b9b784b..9ba44411 100644 --- a/plugins/Voice-Alerts/gradle.properties +++ b/plugins/Voice-Alerts/gradle.properties @@ -1,3 +1,4 @@ android.useAndroidX=true android.nonTransitiveRClass=true -kotlin.code.style=official \ No newline at end of file +kotlin.code.style=official +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8