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
59 changes: 59 additions & 0 deletions .github/workflows/build-addons.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
name: Build addon artifacts

# Builds every addon (or one) to prove the tree compiles and packages.
# It publishes nothing and uploads no artifacts: to get a downloadable URL,
# run "Publish addons" with staging: true.
#
# Two kinds of addon, two build scripts (ADFA-6252). A plugin is a Gradle project
# that compiles to a .cgp against the CodeOnTheGo jars; a template is a plain-text
# tree that zips to a .cgt and needs neither. Each script builds only its own kind
# and ignores a name of the other, so both steps get the same input.

on:
workflow_dispatch:
inputs:
codeonthego_ref:
description: CodeOnTheGo branch or tag to build the libs against
required: false
default: stage
addon:
description: One addon directory name, e.g. Random-XKCD (blank builds all)
required: false
default: ''

permissions:
contents: read

jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- name: Checkout plugin-examples
uses: actions/checkout@v4

- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '17'

- name: Install uv
uses: astral-sh/setup-uv@v5

- name: Set up Gradle
uses: gradle/actions/setup-gradle@v3
with:
cache-disabled: true
add-job-summary: 'never'

- name: Refresh libs and build plugins
env:
CODEONTHEGO_REF: ${{ inputs.codeonthego_ref }}
ONLY_ADDON: ${{ inputs.addon }}
run: ./scripts/build-plugins.sh --ref "$CODEONTHEGO_REF" ${ONLY_ADDON:+"$ONLY_ADDON"}

- name: Build templates
env:
ONLY_ADDON: ${{ inputs.addon }}
run: ./scripts/build-templates.sh ${ONLY_ADDON:+"$ONLY_ADDON"}
87 changes: 0 additions & 87 deletions .github/workflows/build-plugins.yml

This file was deleted.

113 changes: 112 additions & 1 deletion .github/workflows/check-toolchain.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,17 @@ name: Check toolchain
# deliberately not restated here, so there is only one copy of the numbers.
#
# This is the only workflow here that runs automatically on pull requests.
# "Build plugin artifacts" and "Update libs from CodeOnTheGo" are both
# "Build addon artifacts" and "Update libs from CodeOnTheGo" are both
# workflow_dispatch-only, which is why toolchain drift previously reached
# main with no CI signal at all.
#
# It is pure text inspection — no JDK, no Gradle, no network — so it costs a
# few seconds and is safe to make a required check.
#
# It also lints the template bundle sources (ADFA-6252). Those checks are
# borrowed from dev-assets' lint-templates.yml, which guards core.cgt the same
# way. They are cheap, and since this is the only workflow that runs on a pull
# request, it is the only place a broken bundle can be caught before merge.

on:
pull_request:
Expand Down Expand Up @@ -53,3 +58,109 @@ jobs:

- name: Check addon names and metadata
run: uv run --directory tools/addons addons --root "$GITHUB_WORKSPACE" check

# From here down: the template bundle sources. `addons check` already
# asserts the structure (templates.json parses, every path it names
# exists and carries a template.json, the addon.json block is present).
# These add the syntax checks it does not do, on the file contents.
- name: Install the template lint tools
run: |
sudo apt-get update -qq
sudo apt-get install -y -qq libxml2-utils jq zip
pip install --quiet json5

- name: Reject junk files committed under templates/
run: |
set -e
BAD=$(git ls-files templates/ | grep -E '(^|/)(\.gradle|build|local\.properties)(/|$)|\.(swp|swo|bak)$|(^|/)\.DS_Store$' || true)
if [ -n "$BAD" ]; then
echo "::error::Junk files committed under templates/:"
echo "$BAD"
exit 1
fi

- name: Reject a committed bundle or provenance record
run: |
set -e
# Both are generated at publish time. A committed one goes stale
# silently and would ship instead of a fresh build (ADFA-6252).
BAD=$(git ls-files templates/ | grep -E '\.cgt$|(^|/)cgt-build\.properties$' || true)
if [ -n "$BAD" ]; then
echo "::error::A .cgt or cgt-build.properties is committed; both are generated:"
echo "$BAD"
exit 1
fi

- name: Validate XML under templates/
run: |
set -e
fail=0
while IFS= read -r f; do
if ! xmllint --noout "$f" 2>/dev/null; then
echo "::error file=$f::Invalid XML"
fail=1
fi
done < <(find templates -type f -name '*.xml')
exit $fail

- name: Validate each templates.json (strict JSON)
run: |
set -e
fail=0
while IFS= read -r f; do
if ! jq empty "$f" 2>/dev/null; then
echo "::error file=$f::Invalid JSON"
fail=1
fi
done < <(find templates -maxdepth 2 -type f -name 'templates.json')
exit $fail

- name: Validate each template.json (JSON5)
run: |
set -e
fail=0
while IFS= read -r f; do
if ! python3 -c "import json5,sys; json5.load(open(sys.argv[1]))" "$f" >/dev/null 2>&1; then
echo "::error file=$f::Invalid JSON5"
fail=1
fi
done < <(find templates -type f -path '*/template/template.json')
exit $fail

- name: Pebble brace balance check
run: |
set -e
# Counts delimiters only. It cannot tell whether an identifier is
# declared, and the renderer runs with strictVariables(true), so an
# undeclared one still fails on the device at project generation.
fail=0
while IFS= read -r f; do
opens=$(grep -o '{{' "$f" | wc -l | tr -d ' ')
closes=$(grep -o '}}' "$f" | wc -l | tr -d ' ')
if [ "$opens" != "$closes" ]; then
echo "::error file=$f::Pebble brace imbalance: $opens '{{' vs $closes '}}'"
fail=1
fi
done < <(find templates -type f -name '*.peb')
exit $fail

- name: Build every template bundle
run: |
set -euo pipefail
# Proves the bundle packages and that templates.json names only paths
# that exist: build-cgt.sh derives its file list from that manifest and
# fails when one is missing.
./scripts/build-templates.sh
for cgt in dist/*.cgt; do
[ -e "$cgt" ] || continue
slug="$(basename "$cgt" .cgt)"
# The IDE looks templates.json up by that exact bare name, so a
# nested one means no template is ever found.
unzip -l "$cgt" | grep -qE '[[:space:]]templates\.json$' \
|| { echo "::error::${slug}.cgt has no bare templates.json entry"; exit 1; }
# Repository metadata must not reach a user-facing download.
if unzip -l "$cgt" | grep -qE '[[:space:]](addon\.json|.*\.html|icon_(day|night)\.png)$'; then
echo "::error::${slug}.cgt contains gallery metadata that belongs only in the repository"
exit 1
fi
done
44 changes: 17 additions & 27 deletions .github/workflows/publish-addons.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,42 +86,32 @@ jobs:
PLUGIN_LIBS_REVISION: ${{ steps.libs.outputs.revision }}
run: |
set -euo pipefail
mkdir -p dist
all="$(uv run --directory tools/addons addons --root "$GITHUB_WORKSPACE" discover)"
# Without a name, discover lists every addon not held back by skip.txt.
# With one, it resolves the directory name or the slug, in any case, and
# fails on an unknown one, so a hostile value never reaches the shell as
# a path.
if [ "$ADDON" = "all" ]; then
names="$all"
names="$(uv run --directory tools/addons addons --root "$GITHUB_WORKSPACE" discover)"
else
# resolve through discover so an unknown or hostile value cannot
# reach the shell as a path
# match the directory name or the lowercase slug the rest of the
# system uses (dl/<slug>.cgp, the catalog's slug field)
names="$(printf '%s\n' "$all" | awk -v want="$ADDON" '
{ n = $0; sub(/.*\//, "", n); l = tolower(n)
if (n == want || $0 == want || l == tolower(want)) print }')"
if [ -z "$names" ]; then
echo "Unknown addon: $ADDON" >&2; exit 1
fi
names="$(uv run --directory tools/addons addons --root "$GITHUB_WORKSPACE" discover "$ADDON")"
fi
printf 'only<<EOF\n%s\nEOF\n' "$names" >> "$GITHUB_OUTPUT"
for dir in $names; do
echo "==> $dir"
( cd "$dir"
gradlew="$GITHUB_WORKSPACE/gradlew"
if [ -x ./gradlew ]; then gradlew=./gradlew; fi
if grep -q downloadAssets build.gradle.kts; then
"$gradlew" --console=plain downloadAssets
fi
"$gradlew" --console=plain assemblePlugin )
"$GITHUB_WORKSPACE/scripts/verify-provenance.sh" "$dir"
slug="$(basename "$dir" | tr '[:upper:]' '[:lower:]')"
src="$(ls "$dir"/build/plugin/*.cgp | grep -v -- '-debug\.cgp$' | head -n1)"
cp "$src" "dist/${slug}.cgp"
done
# Two kinds of addon, two build scripts (ADFA-6252). Each builds only its
# own kind from the list and ignores the rest. No --ref: the previous
# step already put the plugin-api-latest jars in libs/.
# shellcheck disable=SC2086
./scripts/build-plugins.sh --out dist $names
# shellcheck disable=SC2086
./scripts/build-templates.sh $names

- name: Build the source tarballs
env:
ONLY: ${{ steps.stage.outputs.only }}
run: |
# Templates are passed in with everything else and skipped inside the
# tool, which logs the skip: a template ships no source tarball because
# its .cgt is plain text and already is its source (ADFA-6252). Filtering
# here instead would put the rule in two places.
# shellcheck disable=SC2086
uv run --directory tools/addons addons --root "$GITHUB_WORKSPACE" \
tarball --out "$GITHUB_WORKSPACE/dist" --only $ONLY
Expand Down
6 changes: 4 additions & 2 deletions .github/workflows/update-libs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,10 @@ jobs:
RUN_NUMBER: ${{ github.run_number }}
run: echo "name=build-$(date -u +%Y-%m-%d)-${RUN_NUMBER}" >> "$GITHUB_OUTPUT"

- name: Run update-libs script
run: ./scripts/update-libs.sh
# Refreshes libs/ from CodeOnTheGo@stage, then proves every plugin still
# compiles against the new jars. Templates use no jars, so none are built.
- name: Refresh libs and build plugins
run: ./scripts/build-plugins.sh --ref stage

- name: Commit updated jars
id: commit
Expand Down
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@
**/.DS_Store
**/*.keystore
**/*.cgp
# Template bundles and their provenance record are generated, never committed
# (ADFA-6252). Each template addon also ignores its own, but keep the repo-wide
# rule beside *.cgp so a stray one anywhere cannot be added by accident.
**/*.cgt
**/cgt-build.properties
# Where the publish and build workflows stage artifacts
dist/
**/release.properties
**/RecentProject_database*
**/agent/
Expand Down
Loading