From eb6c6cffc6386568a4ee31cda9c607e39ddd7eb9 Mon Sep 17 00:00:00 2001 From: Jamie Cho Date: Sat, 2 May 2026 16:37:23 -0400 Subject: [PATCH 1/4] Migrate ./build to a Makefile and add smoke tests - Replace ./build with a Makefile exposing help/build/test/push/clean targets. Default target is help. - Add tests/ with four hello-world sources (Java Grinder, BasTo6809, mcbasic, CMOC OS-9). `make test` mounts them read-only into a freshly-built container, runs each toolchain, and verifies outputs exist. - Update README to use `make build` instead of `./build`. - Update build-docker.yml workflow: checkout, build with load:true, run smoke tests, then push image only on tag pushes (build-push-action v6 doesn't allow load+push together). Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/build-docker.yml | 18 +++++++++--- Makefile | 47 ++++++++++++++++++++++++++++++ README.md | 6 +++- build | 4 --- tests/HELLO.BAS | 2 ++ tests/Hello.java | 8 +++++ tests/MC10HELLO.BAS | 2 ++ tests/hello.c | 7 +++++ 8 files changed, 85 insertions(+), 9 deletions(-) create mode 100644 Makefile delete mode 100755 build create mode 100644 tests/HELLO.BAS create mode 100644 tests/Hello.java create mode 100644 tests/MC10HELLO.BAS create mode 100644 tests/hello.c diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index 17c5b5e..adaa7ce 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -37,27 +37,37 @@ jobs: - ubuntu-24.04-arm steps: + - uses: actions/checkout@v4 + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to DockerHub - uses: docker/login-action@v3 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Build and push image + - name: Build and load image env: RELEASE_TAG: ${{ needs.create_tag.outputs.release_tag }} uses: docker/build-push-action@v6 with: - push: ${{ startsWith(github.ref, 'refs/tags') }} + load: true tags: jamieleecho/coco-dev:${{ env.RELEASE_TAG }}-${{ matrix.os }} cache-from: type=gha cache-to: type=gha,mode=max - - uses: actions/checkout@v3 + - name: Smoke test + env: + RELEASE_TAG: ${{ needs.create_tag.outputs.release_tag }} + run: make test TAG=jamieleecho/coco-dev:${{ env.RELEASE_TAG }}-${{ matrix.os }} + + - name: Push image if: startsWith(github.ref, 'refs/tags') + env: + RELEASE_TAG: ${{ needs.create_tag.outputs.release_tag }} + run: docker push jamieleecho/coco-dev:${{ env.RELEASE_TAG }}-${{ matrix.os }} - name: Update repo description uses: peter-evans/dockerhub-description@v3 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2489692 --- /dev/null +++ b/Makefile @@ -0,0 +1,47 @@ +.DEFAULT_GOAL := help +.PHONY: help build test push clean + +IMAGE := jamieleecho/coco-dev +VERSION := $(shell sed -nE 's/.*"version" *: *"([^"]+)".*/\1/p' package.json) +TAG ?= $(IMAGE):$(VERSION) + +help: + @echo "coco-dev Makefile targets:" + @echo "" + @echo " make build Build the docker image and tag as $(TAG)" + @echo " make test Run smoke tests inside the built image" + @echo " make push Push the image to Docker Hub" + @echo " make clean Remove the local image" + @echo " make help Show this help (default)" + @echo "" + @echo "Override the tag with TAG=... (e.g. make test TAG=jamieleecho/coco-dev:0.79)" + +build: + docker compose -f docker-compose.build.yml build + +test: + docker run --rm -v "$(CURDIR)/tests:/sources:ro" $(TAG) bash -euc '\ + work=$$(mktemp -d); \ + cp /sources/* $$work/; \ + cd $$work; \ + echo "[1/4] java_grinder -> .bin"; \ + javac Hello.java; \ + java_grinder Hello.class Hello.asm trs80_coco; \ + naken_asm -l -type bin -o Hello.bin Hello.asm; \ + test -s Hello.bin; \ + echo "[2/4] basto6809todsk -> .DSK"; \ + basto6809todsk HELLO.BAS; \ + test -s HELLO.DSK; \ + echo "[3/4] mcbasic -> .c10"; \ + mcbasic MC10HELLO.BAS; \ + test -s MC10HELLO.c10; \ + echo "[4/4] cmoc --os9 -> OS-9 module"; \ + cmoc --os9 hello.c; \ + test -s hello; \ + echo "All smoke tests passed."' + +push: + docker push $(TAG) + +clean: + -docker rmi $(TAG) diff --git a/README.md b/README.md index 215e5d8..6cf92d6 100644 --- a/README.md +++ b/README.md @@ -75,5 +75,9 @@ your project folder and open the folder in VS Code. See the [documentation](http # Start the Docker application if it is not already running git clone https://github.com/jamieleecho/coco-dev.git cd coco-dev -./build +make build ``` + +Run `make help` to see the available targets. After building, `make test` +runs a quick smoke test that exercises CMOC, BasTo6809, mcbasic, and Java +Grinder against the built image. diff --git a/build b/build deleted file mode 100755 index a393b73..0000000 --- a/build +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env bash - -# Build and tag are now handled in a single step -docker compose -f docker-compose.build.yml build diff --git a/tests/HELLO.BAS b/tests/HELLO.BAS new file mode 100644 index 0000000..8a4715c --- /dev/null +++ b/tests/HELLO.BAS @@ -0,0 +1,2 @@ +10 PRINT "HELLO COCO" +20 END diff --git a/tests/Hello.java b/tests/Hello.java new file mode 100644 index 0000000..7c108a6 --- /dev/null +++ b/tests/Hello.java @@ -0,0 +1,8 @@ +import net.mikekohn.java_grinder.TRS80Coco; + +public class Hello { + public static void main(String[] args) { + TRS80Coco.setText(1024, 0x48); + TRS80Coco.setText(1025, 0x49); + } +} diff --git a/tests/MC10HELLO.BAS b/tests/MC10HELLO.BAS new file mode 100644 index 0000000..9880304 --- /dev/null +++ b/tests/MC10HELLO.BAS @@ -0,0 +1,2 @@ +10 PRINT "HELLO MC-10" +20 END diff --git a/tests/hello.c b/tests/hello.c new file mode 100644 index 0000000..e380a7b --- /dev/null +++ b/tests/hello.c @@ -0,0 +1,7 @@ +#include + +int main(void) +{ + printf("Hello from OS-9\n"); + return 0; +} From 4649e9e45f309d2f6822efd1949abd78abb84846 Mon Sep 17 00:00:00 2001 From: Jamie Cho Date: Sat, 2 May 2026 16:43:20 -0400 Subject: [PATCH 2/4] Add .dockerignore, lint/shell/size targets, organize tests by tool - .dockerignore: excludes everything not needed by the build (Dockerfile only consumes utils/basto6809todsk from the context). - tests/ reorganized into per-tool subfolders: tests/java_grinder/, tests/basto6809/, tests/mcbasic/, tests/cmoc-os9/ - Makefile additions: make shell one-off bash shell in the image make lint shellcheck via koalaman/shellcheck-alpine make size prints image size in MB - Fix shellcheck issues in coco-dev and utils/basto6809todsk: - Use "$@" array instead of "$*" so args with spaces are preserved - Quote variable expansions - Replace legacy backticks with $(...) - Add `set -eu` to basto6809todsk - /bin/env -> /usr/bin/env shebang (POSIX) - CI: add a fast `lint` job and an Image size step after smoke tests. Co-Authored-By: Claude Opus 4.7 (1M context) --- .dockerignore | 11 +++++++++++ .github/workflows/build-docker.yml | 11 +++++++++++ Makefile | 24 ++++++++++++++++++++---- coco-dev | 11 +++++------ tests/{ => basto6809}/HELLO.BAS | 0 tests/{ => cmoc-os9}/hello.c | 0 tests/{ => java_grinder}/Hello.java | 0 tests/{ => mcbasic}/MC10HELLO.BAS | 0 utils/basto6809todsk | 21 +++++++++++---------- 9 files changed, 58 insertions(+), 20 deletions(-) create mode 100644 .dockerignore rename tests/{ => basto6809}/HELLO.BAS (100%) rename tests/{ => cmoc-os9}/hello.c (100%) rename tests/{ => java_grinder}/Hello.java (100%) rename tests/{ => mcbasic}/MC10HELLO.BAS (100%) diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..477752a --- /dev/null +++ b/.dockerignore @@ -0,0 +1,11 @@ +.dockerignore +.git +.github +.gitignore +Makefile +README.md +bump-info.json +coco-dev +docker-compose.build.yml +package.json +tests diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index adaa7ce..fb31cf8 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -12,6 +12,12 @@ permissions: contents: write jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - run: make lint + create_tag: runs-on: ubuntu-latest outputs: @@ -63,6 +69,11 @@ jobs: RELEASE_TAG: ${{ needs.create_tag.outputs.release_tag }} run: make test TAG=jamieleecho/coco-dev:${{ env.RELEASE_TAG }}-${{ matrix.os }} + - name: Image size + env: + RELEASE_TAG: ${{ needs.create_tag.outputs.release_tag }} + run: make size TAG=jamieleecho/coco-dev:${{ env.RELEASE_TAG }}-${{ matrix.os }} + - name: Push image if: startsWith(github.ref, 'refs/tags') env: diff --git a/Makefile b/Makefile index 2489692..6ba4b02 100644 --- a/Makefile +++ b/Makefile @@ -1,15 +1,19 @@ .DEFAULT_GOAL := help -.PHONY: help build test push clean +.PHONY: help build test shell lint size push clean IMAGE := jamieleecho/coco-dev VERSION := $(shell sed -nE 's/.*"version" *: *"([^"]+)".*/\1/p' package.json) TAG ?= $(IMAGE):$(VERSION) +SHELLCHECK_IMAGE := koalaman/shellcheck-alpine:stable help: @echo "coco-dev Makefile targets:" @echo "" @echo " make build Build the docker image and tag as $(TAG)" @echo " make test Run smoke tests inside the built image" + @echo " make shell Drop into a one-off bash shell in the image" + @echo " make lint Run shellcheck on the shell scripts" + @echo " make size Print the size of the built image" @echo " make push Push the image to Docker Hub" @echo " make clean Remove the local image" @echo " make help Show this help (default)" @@ -21,25 +25,37 @@ build: test: docker run --rm -v "$(CURDIR)/tests:/sources:ro" $(TAG) bash -euc '\ - work=$$(mktemp -d); \ - cp /sources/* $$work/; \ - cd $$work; \ echo "[1/4] java_grinder -> .bin"; \ + work=$$(mktemp -d); cp /sources/java_grinder/* $$work/; cd $$work; \ javac Hello.java; \ java_grinder Hello.class Hello.asm trs80_coco; \ naken_asm -l -type bin -o Hello.bin Hello.asm; \ test -s Hello.bin; \ echo "[2/4] basto6809todsk -> .DSK"; \ + work=$$(mktemp -d); cp /sources/basto6809/* $$work/; cd $$work; \ basto6809todsk HELLO.BAS; \ test -s HELLO.DSK; \ echo "[3/4] mcbasic -> .c10"; \ + work=$$(mktemp -d); cp /sources/mcbasic/* $$work/; cd $$work; \ mcbasic MC10HELLO.BAS; \ test -s MC10HELLO.c10; \ echo "[4/4] cmoc --os9 -> OS-9 module"; \ + work=$$(mktemp -d); cp /sources/cmoc-os9/* $$work/; cd $$work; \ cmoc --os9 hello.c; \ test -s hello; \ echo "All smoke tests passed."' +shell: + docker run --rm -it $(TAG) bash + +lint: + docker run --rm -v "$(CURDIR):/work" -w /work $(SHELLCHECK_IMAGE) \ + shellcheck coco-dev utils/basto6809todsk + +size: + @bytes=$$(docker image inspect --format='{{.Size}}' $(TAG)); \ + awk -v b=$$bytes 'BEGIN { printf "%s: %.2f MB\n", "$(TAG)", b/1024/1024 }' + push: docker push $(TAG) diff --git a/coco-dev b/coco-dev index 37c55d2..3fd74fe 100755 --- a/coco-dev +++ b/coco-dev @@ -1,8 +1,8 @@ #!/usr/bin/env bash -if [[ $* = *[!\ ]* ]]; then - params=( "$*" ) +if [[ $# -gt 0 ]]; then + params=( "$@" ) else params=( bash ) fi @@ -12,11 +12,11 @@ COCO_DEV_IMAGE=jamieleecho/coco-dev:0.79 case "$(uname -s)" in Darwin) - docker run -it --rm -w "$(pwd | sed -e s#^/[Uu][Ss][Ee][Rr][Ss]#/home#)" -v "$HOME"/..:/home -e HOME=/home/"$USER" ${COCO_DEV_IMAGE} ${params[@]} + docker run -it --rm -w "$(pwd | sed -e s#^/[Uu][Ss][Ee][Rr][Ss]#/home#)" -v "$HOME"/..:/home -e HOME=/home/"$USER" "${COCO_DEV_IMAGE}" "${params[@]}" ;; Linux) - docker run -u "$(id -u):$(id -g)" -it --rm -w "$(pwd)" -v /etc/passwd:/etc/passwd -v "$HOME"/..:/home -e HOME=/home/"$USER" ${COCO_DEV_IMAGE} ${params[@]} + docker run -u "$(id -u):$(id -g)" -it --rm -w "$(pwd)" -v /etc/passwd:/etc/passwd -v "$HOME"/..:/home -e HOME=/home/"$USER" "${COCO_DEV_IMAGE}" "${params[@]}" ;; CYGWIN*|MINGW32*|MSYS*) @@ -27,7 +27,6 @@ case "$(uname -s)" in # See correspondence table at the bottom of this answer *) - echo 'Unknown OS not supported' + echo 'Unknown OS not supported' ;; esac - diff --git a/tests/HELLO.BAS b/tests/basto6809/HELLO.BAS similarity index 100% rename from tests/HELLO.BAS rename to tests/basto6809/HELLO.BAS diff --git a/tests/hello.c b/tests/cmoc-os9/hello.c similarity index 100% rename from tests/hello.c rename to tests/cmoc-os9/hello.c diff --git a/tests/Hello.java b/tests/java_grinder/Hello.java similarity index 100% rename from tests/Hello.java rename to tests/java_grinder/Hello.java diff --git a/tests/MC10HELLO.BAS b/tests/mcbasic/MC10HELLO.BAS similarity index 100% rename from tests/MC10HELLO.BAS rename to tests/mcbasic/MC10HELLO.BAS diff --git a/utils/basto6809todsk b/utils/basto6809todsk index 148f98d..83c4cca 100755 --- a/utils/basto6809todsk +++ b/utils/basto6809todsk @@ -1,27 +1,28 @@ -#!/bin/env bash +#!/usr/bin/env bash +set -eu if [[ $# -ne 1 ]] ; then - echo $0 PROG.BAS - echo Compiles PROG.BAS into a binary located on PROG.DSK + echo "$0 PROG.BAS" + echo "Compiles PROG.BAS into a binary located on PROG.DSK" exit 0 fi BAS_FILE="$1" -BAS_FILE0=$(basename ${BAS_FILE}) +BAS_FILE0=$(basename "${BAS_FILE}") ASM_FILE="${BAS_FILE0%.BAS}.asm" BIN_FILE="${BAS_FILE0%.BAS}.BIN" DSK_FILE="${BAS_FILE0%.BAS}.DSK" -CURRENT_DIR="`pwd`" +CURRENT_DIR="$(pwd)" # Create a working folder tmpdir=$(mktemp -d 2>/dev/null || mktemp -d -t 'tmpdir') -echo Copying compiler and source file to ${tmpdir} -cp -R /usr/local/share/basto6809/* ${tmpdir} -cp $1 ${tmpdir} -cd ${tmpdir} +echo "Copying compiler and source file to ${tmpdir}" +cp -R /usr/local/share/basto6809/* "${tmpdir}" +cp "$1" "${tmpdir}" +cd "${tmpdir}" # Do real work -echo Compiling ${BAS_FILE} and creating ${ASM_FILE}, ${BIN_FILE} and ${DSK_FILE} +echo "Compiling ${BAS_FILE} and creating ${ASM_FILE}, ${BIN_FILE} and ${DSK_FILE}" ./BasTo6809 "${BAS_FILE0}" lwasm -o "${BIN_FILE}" "$ASM_FILE" decb dskini "${DSK_FILE}" From 0b3ee6196f3be9d50c30642259598f2ad1b16ae4 Mon Sep 17 00:00:00 2001 From: Jamie Cho Date: Sat, 2 May 2026 16:46:40 -0400 Subject: [PATCH 3/4] Run CI on push only, add concurrency group MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pushing to a branch with an open PR was firing both `push` and `pull_request: synchronize` — double the CI minutes for the same commit. Drop the pull_request trigger; push-trigger covers every same-repo branch. Concurrency group cancels superseded runs when multiple commits land in quick succession. Tradeoff: PRs from forks no longer auto-trigger CI (they wouldn't have had access to DOCKERHUB_TOKEN anyway). Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/build-docker.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index fb31cf8..73b64d9 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -1,13 +1,16 @@ name: Build Docker Image on: - pull_request: push: branches: - '**' - '!main' workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + permissions: contents: write From 72562366815fc0d3939362b2b8164056e2fdb04d Mon Sep 17 00:00:00 2001 From: Jamie Cho Date: Sat, 2 May 2026 16:56:44 -0400 Subject: [PATCH 4/4] Scope buildx cache per arch to fix 'failed to reserve cache' Both matrix jobs (ubuntu-24.04 and ubuntu-24.04-arm) were writing to the default GHA cache slot in parallel; the second to start would race and fail with `error writing layer blob: failed to reserve cache`. Giving each arch its own scope eliminates the collision. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/build-docker.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index 73b64d9..04b7089 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -64,8 +64,8 @@ jobs: with: load: true tags: jamieleecho/coco-dev:${{ env.RELEASE_TAG }}-${{ matrix.os }} - cache-from: type=gha - cache-to: type=gha,mode=max + cache-from: type=gha,scope=${{ matrix.os }} + cache-to: type=gha,mode=max,scope=${{ matrix.os }} - name: Smoke test env: