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
48 changes: 20 additions & 28 deletions .github/workflows/publish-addons.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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@<sha>' 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
Expand Down
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/<Addon> && ../../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

Expand Down Expand Up @@ -118,7 +118,7 @@ unzip -p <plugin>/build/plugin/<name>.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.

Expand Down
1 change: 1 addition & 0 deletions plugins/AI-Code-Suggestions/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
android.useAndroidX=true
android.nonTransitiveRClass=true
kotlin.code.style=official
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
1 change: 1 addition & 0 deletions plugins/AI-Literacy-Course/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
android.useAndroidX=true
android.nonTransitiveRClass=true
kotlin.code.style=official
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
1 change: 1 addition & 0 deletions plugins/Icons-Repository/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
android.useAndroidX=true
android.nonTransitiveRClass=true
kotlin.code.style=official
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
1 change: 1 addition & 0 deletions plugins/Rainbow-Brackets/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
android.useAndroidX=true
android.nonTransitiveRClass=true
kotlin.code.style=official
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
1 change: 1 addition & 0 deletions plugins/Random-XKCD/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
android.useAndroidX=true
android.nonTransitiveRClass=true
kotlin.code.style=official
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
1 change: 1 addition & 0 deletions plugins/Vector-Search/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
android.useAndroidX=true
android.nonTransitiveRClass=true
kotlin.code.style=official
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
3 changes: 2 additions & 1 deletion plugins/Voice-Alerts/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
android.useAndroidX=true
android.nonTransitiveRClass=true
kotlin.code.style=official
kotlin.code.style=official
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
Loading