Add fused desktop binaries (win/mac/linux) to the pipeline
Fuse the .love into the official LÖVE 11.5 release artifacts: win-x64 (love.exe concat + dlls), mac-universal (love.app bundle, Info.plist patched) and linux-x64 (AppImage rebuilt via readelf offset + unsquashfs/mksquashfs — the AppImage runtime is glibc-linked and cannot run on the alpine builder). The builder image pre-fetches the dist files to /opt/love-dist and gains squashfs-tools + binutils. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,13 +4,13 @@ CI/CD templates for [LÖVE](https://love2d.org) (Love2D/Lua) game
|
|||||||
projects, in the same spirit as [tic80-tools](../tic80-tools).
|
projects, in the same spirit as [tic80-tools](../tic80-tools).
|
||||||
|
|
||||||
- `example-makefile.make` — project Makefile: local dev targets
|
- `example-makefile.make` — project Makefile: local dev targets
|
||||||
(`build`, `love`, `web`, `export`, `watch`, `clean`) plus the
|
(`build`, `love`, `web`, `export`, `binaries`, `watch`, `clean`)
|
||||||
Woodpecker pipeline targets (`ci-version`, `ci-export`, `ci-upload`,
|
plus the Woodpecker pipeline targets (`ci-version`, `ci-export`,
|
||||||
`ci-update`).
|
`ci-binaries`, `ci-upload`, `ci-update`).
|
||||||
- `example-woodpecker.yaml` — Woodpecker pipeline: version → export
|
- `example-woodpecker.yaml` — Woodpecker pipeline: version → export
|
||||||
(`.love` package + love.js web build) → upload (scp to the droparea)
|
(`.love` package + love.js web build) → binaries (fused desktop
|
||||||
→ update (calls the teletypegames `/update` endpoint with
|
builds) → upload (scp to the droparea) → update (calls the
|
||||||
`platform=love`).
|
teletypegames `/update` endpoint with `platform=love`).
|
||||||
- `example-tasks.json` — VS Code `.vscode/tasks.json` with the common
|
- `example-tasks.json` — VS Code `.vscode/tasks.json` with the common
|
||||||
dev tasks: **Run Löve** (`love .`, default build task,
|
dev tasks: **Run Löve** (`love .`, default build task,
|
||||||
`Cmd+Shift+B`), **Build & Run Löve**, **Build .love package** and
|
`Cmd+Shift+B`), **Build & Run Löve**, **Build .love package** and
|
||||||
@@ -25,16 +25,40 @@ projects, in the same spirit as [tic80-tools](../tic80-tools).
|
|||||||
3. Copy `example-tasks.json` to `.vscode/tasks.json`.
|
3. Copy `example-tasks.json` to `.vscode/tasks.json`.
|
||||||
|
|
||||||
The pipeline uploads `$(PROJECT)-$(VERSION).love.zip`,
|
The pipeline uploads `$(PROJECT)-$(VERSION).love.zip`,
|
||||||
`$(PROJECT)-$(VERSION).html.zip` and the versioned `metadata.json` to
|
`$(PROJECT)-$(VERSION).html.zip`, the versioned `metadata.json` and
|
||||||
the droparea, then triggers `/update` twice: once with `platform=love`
|
the fused binary zips (`-win-x64.zip`, `-mac-universal.zip`,
|
||||||
(desktop package) and once with `platform=love-web` (web build).
|
`-linux-x64.zip`) to the droparea, then triggers `/update` twice: once
|
||||||
|
with `platform=love` (desktop package) and once with
|
||||||
|
`platform=love-web` (web build). The API's `BinaryAttachment` concern
|
||||||
|
picks up the `<name>-<version>-<slug>.zip` files automatically and
|
||||||
|
registers them as `win_x64` / `mac_universal` / `linux_x64` release
|
||||||
|
assets.
|
||||||
|
|
||||||
|
## Native (fused) binaries
|
||||||
|
|
||||||
|
`make binaries VERSION=x.y` builds desktop packages from the official
|
||||||
|
LÖVE $(LOVE_VERSION) release artifacts — no compilation involved:
|
||||||
|
|
||||||
|
- **win-x64** — `love.exe` + the `.love` concatenated (the standard
|
||||||
|
LÖVE fusing mechanism), zipped together with the dlls.
|
||||||
|
- **mac-universal** — the `.love` dropped into the official
|
||||||
|
`love.app` (a universal x86_64+arm64 bundle), `Info.plist` patched
|
||||||
|
to the project name/identifier. Unsigned — Gatekeeper will warn on
|
||||||
|
first launch.
|
||||||
|
- **linux-x64** — the official AppImage taken apart, the `.love`
|
||||||
|
fused into `bin/love`, then reassembled. The AppImage runtime is
|
||||||
|
glibc-linked and cannot be executed on alpine, so the Makefile
|
||||||
|
computes the squashfs offset with `readelf` and repacks with
|
||||||
|
`mksquashfs` instead of running `--appimage-extract`.
|
||||||
|
|
||||||
## Builder image
|
## Builder image
|
||||||
|
|
||||||
`builder.Dockerfile` bakes the pipeline's export-step dependencies
|
`builder.Dockerfile` bakes the pipeline's export-step dependencies
|
||||||
(zip/unzip/curl/make + `luac` for the syntax check) into
|
(zip/unzip/curl/make + `luac` for the syntax check, squashfs-tools +
|
||||||
|
binutils for the AppImage fusing) into
|
||||||
`git.teletypegames.org/internal/love-builder`, and pre-fetches love.js to
|
`git.teletypegames.org/internal/love-builder`, and pre-fetches love.js to
|
||||||
`/opt/lovejs.zip` — the Makefile uses the cached copy when present
|
`/opt/lovejs.zip` plus the official LÖVE dist files to
|
||||||
|
`/opt/love-dist/` — the Makefile uses the cached copies when present
|
||||||
and only falls back to downloading from GitHub outside the image:
|
and only falls back to downloading from GitHub outside the image:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -43,6 +67,8 @@ docker build --platform linux/amd64 -f builder.Dockerfile \
|
|||||||
docker push git.teletypegames.org/internal/love-builder:latest
|
docker push git.teletypegames.org/internal/love-builder:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
Rebuild the image to pick up a newer love.js snapshot.
|
Rebuild the image to pick up a newer love.js snapshot or LÖVE
|
||||||
|
release (bump `LOVE_VERSION` in the Dockerfile and the Makefile
|
||||||
|
together).
|
||||||
|
|
||||||
Detailed documentation lives on the wiki: `/development/love`.
|
Detailed documentation lives on the wiki: `/development/love`.
|
||||||
|
|||||||
+16
-3
@@ -1,6 +1,11 @@
|
|||||||
# CI builder image for Love2D projects: packaging tools + luac syntax check,
|
# CI builder image for Love2D projects: packaging tools + luac syntax check,
|
||||||
# with love.js pre-fetched to /opt/lovejs.zip so `make web` does not hit
|
# with love.js and the official LÖVE dist files pre-fetched so the pipeline
|
||||||
# GitHub on every pipeline run (the Makefile falls back to curl elsewhere).
|
# does not hit GitHub on every run (the Makefile falls back to curl elsewhere).
|
||||||
|
#
|
||||||
|
# squashfs-tools + binutils are needed for the linux AppImage fusing:
|
||||||
|
# the AppImage runtime is glibc-linked so it cannot be executed on alpine —
|
||||||
|
# the Makefile computes the squashfs offset with readelf and repacks with
|
||||||
|
# mksquashfs instead.
|
||||||
#
|
#
|
||||||
# Build & push (the CI runner is linux/amd64):
|
# Build & push (the CI runner is linux/amd64):
|
||||||
# docker build --platform linux/amd64 -f builder.Dockerfile \
|
# docker build --platform linux/amd64 -f builder.Dockerfile \
|
||||||
@@ -9,13 +14,21 @@
|
|||||||
|
|
||||||
FROM alpine:3.22
|
FROM alpine:3.22
|
||||||
|
|
||||||
RUN apk add --no-cache make zip unzip curl git lua5.4 \
|
ARG LOVE_VERSION=11.5
|
||||||
|
|
||||||
|
RUN apk add --no-cache make zip unzip curl git lua5.4 squashfs-tools binutils \
|
||||||
&& ln -sf /usr/bin/lua5.4 /usr/bin/lua \
|
&& ln -sf /usr/bin/lua5.4 /usr/bin/lua \
|
||||||
&& ln -sf /usr/bin/luac5.4 /usr/bin/luac
|
&& ln -sf /usr/bin/luac5.4 /usr/bin/luac
|
||||||
|
|
||||||
RUN curl -sSL https://github.com/2dengine/love.js/archive/refs/heads/master.zip \
|
RUN curl -sSL https://github.com/2dengine/love.js/archive/refs/heads/master.zip \
|
||||||
-o /opt/lovejs.zip
|
-o /opt/lovejs.zip
|
||||||
|
|
||||||
|
RUN mkdir -p /opt/love-dist \
|
||||||
|
&& cd /opt/love-dist \
|
||||||
|
&& curl -sSLO https://github.com/love2d/love/releases/download/${LOVE_VERSION}/love-${LOVE_VERSION}-win64.zip \
|
||||||
|
&& curl -sSLO https://github.com/love2d/love/releases/download/${LOVE_VERSION}/love-${LOVE_VERSION}-macos.zip \
|
||||||
|
&& curl -sSLO https://github.com/love2d/love/releases/download/${LOVE_VERSION}/love-${LOVE_VERSION}-x86_64.AppImage
|
||||||
|
|
||||||
WORKDIR /workspace
|
WORKDIR /workspace
|
||||||
|
|
||||||
CMD ["/bin/sh"]
|
CMD ["/bin/sh"]
|
||||||
|
|||||||
+101
-2
@@ -59,6 +59,97 @@ web: love
|
|||||||
sed -i.bak 's|<base href="/play/">|<base href="/file/$(PROJECT)-$(VERSION)/">|g' $(WEB_DIR)/index.html && rm $(WEB_DIR)/index.html.bak
|
sed -i.bak 's|<base href="/play/">|<base href="/file/$(PROJECT)-$(VERSION)/">|g' $(WEB_DIR)/index.html && rm $(WEB_DIR)/index.html.bak
|
||||||
@echo "==> Web build ready in $(WEB_DIR)"
|
@echo "==> Web build ready in $(WEB_DIR)"
|
||||||
|
|
||||||
|
# -----------------------------------------
|
||||||
|
# Native (fused) binaries from the official LÖVE releases.
|
||||||
|
# The love-builder CI image pre-fetches the dist files to /opt/love-dist;
|
||||||
|
# local builds fall back to downloading them from GitHub.
|
||||||
|
# win: love.exe + .love concatenated; mac: .love dropped into the
|
||||||
|
# official (universal) love.app; linux: the official AppImage taken
|
||||||
|
# apart, the .love fused into bin/love, then reassembled with
|
||||||
|
# mksquashfs (squashfs-tools required).
|
||||||
|
# -----------------------------------------
|
||||||
|
|
||||||
|
LOVE_VERSION = 11.5
|
||||||
|
LOVE_BASE_URL = https://github.com/love2d/love/releases/download/$(LOVE_VERSION)
|
||||||
|
LOVE_CACHE_DIR = /opt/love-dist
|
||||||
|
|
||||||
|
BINARY_SLUGS = win-x64 mac-universal linux-x64
|
||||||
|
|
||||||
|
# $(1) dist file name
|
||||||
|
define f_fetch_love
|
||||||
|
mkdir -p $(DIST_DIR); \
|
||||||
|
if [ -f "$(LOVE_CACHE_DIR)/$(1)" ]; then \
|
||||||
|
echo "==> Using cached $(1)"; \
|
||||||
|
cp "$(LOVE_CACHE_DIR)/$(1)" "$(DIST_DIR)/$(1)"; \
|
||||||
|
elif [ ! -f "$(DIST_DIR)/$(1)" ]; then \
|
||||||
|
echo "==> Downloading $(1)"; \
|
||||||
|
curl -sSL "$(LOVE_BASE_URL)/$(1)" -o "$(DIST_DIR)/$(1)"; \
|
||||||
|
fi
|
||||||
|
endef
|
||||||
|
|
||||||
|
binaries:
|
||||||
|
@if [ -z "$(VERSION)" ]; then \
|
||||||
|
echo "ERROR: VERSION not set!"; exit 1; \
|
||||||
|
fi
|
||||||
|
@$(MAKE) binary-win binary-mac binary-linux VERSION=$(VERSION)
|
||||||
|
|
||||||
|
binary-win: love
|
||||||
|
@echo "==> Fusing windows binary"
|
||||||
|
@$(call f_fetch_love,love-$(LOVE_VERSION)-win64.zip)
|
||||||
|
@set -e; \
|
||||||
|
PKG_DIR="$(PROJECT)-$(VERSION)-win-x64"; \
|
||||||
|
rm -rf "$$PKG_DIR" "$$PKG_DIR.zip" $(DIST_DIR)/win64; \
|
||||||
|
unzip -q $(DIST_DIR)/love-$(LOVE_VERSION)-win64.zip -d $(DIST_DIR)/win64; \
|
||||||
|
SRC=$$(dirname $$(find $(DIST_DIR)/win64 -name love.exe | head -n 1)); \
|
||||||
|
mkdir -p "$$PKG_DIR"; \
|
||||||
|
cat "$$SRC/love.exe" $(OUTPUT_LOVE) > "$$PKG_DIR/$(PROJECT).exe"; \
|
||||||
|
cp "$$SRC"/*.dll "$$PKG_DIR/"; \
|
||||||
|
cp "$$SRC/license.txt" "$$PKG_DIR/" 2>/dev/null || true; \
|
||||||
|
zip -qr "$$PKG_DIR.zip" "$$PKG_DIR"; \
|
||||||
|
rm -rf "$$PKG_DIR" $(DIST_DIR)/win64; \
|
||||||
|
echo "==> $$PKG_DIR.zip kesz"
|
||||||
|
|
||||||
|
binary-mac: love
|
||||||
|
@echo "==> Fusing macOS app bundle"
|
||||||
|
@$(call f_fetch_love,love-$(LOVE_VERSION)-macos.zip)
|
||||||
|
@set -e; \
|
||||||
|
PKG_DIR="$(PROJECT)-$(VERSION)-mac-universal"; \
|
||||||
|
rm -rf "$$PKG_DIR" "$$PKG_DIR.zip" $(DIST_DIR)/macos; \
|
||||||
|
unzip -q $(DIST_DIR)/love-$(LOVE_VERSION)-macos.zip -d $(DIST_DIR)/macos; \
|
||||||
|
mkdir -p "$$PKG_DIR"; \
|
||||||
|
mv $(DIST_DIR)/macos/love.app "$$PKG_DIR/$(PROJECT).app"; \
|
||||||
|
cp $(OUTPUT_LOVE) "$$PKG_DIR/$(PROJECT).app/Contents/Resources/"; \
|
||||||
|
PLIST="$$PKG_DIR/$(PROJECT).app/Contents/Info.plist"; \
|
||||||
|
sed -i.bak "s|<string>LÖVE</string>|<string>$(PROJECT)</string>|g" "$$PLIST" && rm "$$PLIST.bak"; \
|
||||||
|
sed -i.bak "s|org\.love2d\.love|org.teletypegames.$(PROJECT)|g" "$$PLIST" && rm "$$PLIST.bak"; \
|
||||||
|
zip -qry "$$PKG_DIR.zip" "$$PKG_DIR"; \
|
||||||
|
rm -rf "$$PKG_DIR" $(DIST_DIR)/macos; \
|
||||||
|
echo "==> $$PKG_DIR.zip kesz"
|
||||||
|
|
||||||
|
# Az AppImage runtime glibc-dinamikus, alpine (musl) alatt nem futtatható,
|
||||||
|
# ezért nem a runtime-ot futtatjuk: az offsetet readelf-ből számoljuk
|
||||||
|
# (shoff + shentsize*shnum), a squashfs-t unsquashfs -o bontja ki.
|
||||||
|
binary-linux: love
|
||||||
|
@echo "==> Fusing linux AppImage"
|
||||||
|
@$(call f_fetch_love,love-$(LOVE_VERSION)-x86_64.AppImage)
|
||||||
|
@set -e; \
|
||||||
|
PKG_DIR="$(PROJECT)-$(VERSION)-linux-x64"; \
|
||||||
|
APPIMAGE="$(DIST_DIR)/love-$(LOVE_VERSION)-x86_64.AppImage"; \
|
||||||
|
rm -rf "$$PKG_DIR" "$$PKG_DIR.zip" squashfs-root $(DIST_DIR)/game.squashfs $(DIST_DIR)/runtime; \
|
||||||
|
OFFSET=$$(readelf -h "$$APPIMAGE" | awk '/Start of section headers/{o=$$5} /Size of section headers/{s=$$5} /Number of section headers/{n=$$5} END{print o+s*n}'); \
|
||||||
|
unsquashfs -q -o $$OFFSET -d squashfs-root "$$APPIMAGE" >/dev/null; \
|
||||||
|
cat squashfs-root/bin/love $(OUTPUT_LOVE) > squashfs-root/bin/love.fused; \
|
||||||
|
mv squashfs-root/bin/love.fused squashfs-root/bin/love; \
|
||||||
|
chmod +x squashfs-root/bin/love; \
|
||||||
|
mksquashfs squashfs-root $(DIST_DIR)/game.squashfs -root-owned -noappend -quiet -comp gzip; \
|
||||||
|
head -c $$OFFSET "$$APPIMAGE" > $(DIST_DIR)/runtime; \
|
||||||
|
mkdir -p "$$PKG_DIR"; \
|
||||||
|
cat $(DIST_DIR)/runtime $(DIST_DIR)/game.squashfs > "$$PKG_DIR/$(PROJECT).AppImage"; \
|
||||||
|
chmod +x "$$PKG_DIR/$(PROJECT).AppImage"; \
|
||||||
|
zip -qr "$$PKG_DIR.zip" "$$PKG_DIR"; \
|
||||||
|
rm -rf "$$PKG_DIR" squashfs-root $(DIST_DIR)/game.squashfs $(DIST_DIR)/runtime; \
|
||||||
|
echo "==> $$PKG_DIR.zip kesz"
|
||||||
|
|
||||||
export: love web
|
export: love web
|
||||||
@if [ -z "$(VERSION)" ]; then \
|
@if [ -z "$(VERSION)" ]; then \
|
||||||
echo "ERROR: VERSION not set!"; exit 1; \
|
echo "ERROR: VERSION not set!"; exit 1; \
|
||||||
@@ -93,6 +184,10 @@ ci-export:
|
|||||||
@VERSION=$$(cat $(VERSION_FILE)); \
|
@VERSION=$$(cat $(VERSION_FILE)); \
|
||||||
$(MAKE) export VERSION=$$VERSION
|
$(MAKE) export VERSION=$$VERSION
|
||||||
|
|
||||||
|
ci-binaries:
|
||||||
|
@VERSION=$$(cat $(VERSION_FILE)); \
|
||||||
|
$(MAKE) binaries VERSION=$$VERSION
|
||||||
|
|
||||||
ci-upload:
|
ci-upload:
|
||||||
@VERSION=$$(cat $(VERSION_FILE)); \
|
@VERSION=$$(cat $(VERSION_FILE)); \
|
||||||
FILE_LOVE="$(PROJECT)-$$VERSION.love.zip"; \
|
FILE_LOVE="$(PROJECT)-$$VERSION.love.zip"; \
|
||||||
@@ -100,8 +195,12 @@ ci-upload:
|
|||||||
META_SRC="metadata.json"; \
|
META_SRC="metadata.json"; \
|
||||||
META_DST="$(PROJECT)-$$VERSION.metadata.json"; \
|
META_DST="$(PROJECT)-$$VERSION.metadata.json"; \
|
||||||
cp $$META_SRC $$META_DST; \
|
cp $$META_SRC $$META_DST; \
|
||||||
|
BINS=""; \
|
||||||
|
for slug in $(BINARY_SLUGS); do \
|
||||||
|
[ -f "$(PROJECT)-$$VERSION-$$slug.zip" ] && BINS="$$BINS $(PROJECT)-$$VERSION-$$slug.zip"; \
|
||||||
|
done; \
|
||||||
sshpass -p "$(DROPAREA_SSH_PASSWORD)" scp -o StrictHostKeyChecking=no -P $(DROPAREA_PORT) \
|
sshpass -p "$(DROPAREA_SSH_PASSWORD)" scp -o StrictHostKeyChecking=no -P $(DROPAREA_PORT) \
|
||||||
$$FILE_LOVE $$FILE_WEB $$META_DST \
|
$$FILE_LOVE $$FILE_WEB $$META_DST $$BINS \
|
||||||
$(DROPAREA_USER)@$(DROPAREA_HOST):$(DROPAREA_TARGET_PATH)/
|
$(DROPAREA_USER)@$(DROPAREA_HOST):$(DROPAREA_TARGET_PATH)/
|
||||||
|
|
||||||
ci-update:
|
ci-update:
|
||||||
@@ -109,4 +208,4 @@ ci-update:
|
|||||||
curl "$(UPDATE_SERVER)/update?secret=$(UPDATE_SECRET)&name=$(PROJECT)&platform=love&version=$$VERSION"; \
|
curl "$(UPDATE_SERVER)/update?secret=$(UPDATE_SECRET)&name=$(PROJECT)&platform=love&version=$$VERSION"; \
|
||||||
curl "$(UPDATE_SERVER)/update?secret=$(UPDATE_SECRET)&name=$(PROJECT)&platform=love-web&version=$$VERSION"
|
curl "$(UPDATE_SERVER)/update?secret=$(UPDATE_SECRET)&name=$(PROJECT)&platform=love-web&version=$$VERSION"
|
||||||
|
|
||||||
.PHONY: all build love web export watch clean ci-version ci-export ci-upload ci-update
|
.PHONY: all build love web export binaries binary-win binary-mac binary-linux watch clean ci-version ci-export ci-binaries ci-upload ci-update
|
||||||
@@ -10,6 +10,11 @@ steps:
|
|||||||
commands:
|
commands:
|
||||||
- make ci-export
|
- make ci-export
|
||||||
|
|
||||||
|
- name: binaries
|
||||||
|
image: git.teletypegames.org/internal/love-builder:latest
|
||||||
|
commands:
|
||||||
|
- make ci-binaries
|
||||||
|
|
||||||
- name: upload
|
- name: upload
|
||||||
image: alpine
|
image: alpine
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
Reference in New Issue
Block a user