diff --git a/.gitea/workflows/.check.yaml.swp b/.gitea/workflows/.check.yaml.swp new file mode 100644 index 0000000..c39eff5 Binary files /dev/null and b/.gitea/workflows/.check.yaml.swp differ diff --git a/.gitea/workflows/check.yaml b/.gitea/workflows/check.yaml new file mode 100644 index 0000000..5316fb7 --- /dev/null +++ b/.gitea/workflows/check.yaml @@ -0,0 +1,29 @@ +name: check + +on: + push: + workflow_dispatch: + +jobs: + # Runs on the existing Linux runners. Wakeful links AppKit, IOKit and Carbon, + # so this can parse the Swift but not compile it; see macos.yaml. + lint: + runs-on: ubuntu-latest + container: + image: swift:6.3 + steps: + # actions/checkout needs Node, which the Swift image doesn't have. + - name: Checkout + env: + TOKEN: ${{ gitea.token }} + run: | + git config --global --add safe.directory "$PWD" + git init -q . + auth=$(printf 'x-access-token:%s' "$TOKEN" | base64 | tr -d '\n') + git -c http.extraHeader="Authorization: Basic $auth" \ + fetch -q --depth=1 "${{ gitea.server_url }}/${{ gitea.repository }}.git" "${{ gitea.sha }}" + git checkout -q FETCH_HEAD + git log -1 --oneline + + - name: Check + run: scripts/check.sh diff --git a/.gitea/workflows/macos.yaml b/.gitea/workflows/macos.yaml new file mode 100644 index 0000000..3f73056 --- /dev/null +++ b/.gitea/workflows/macos.yaml @@ -0,0 +1,24 @@ +name: macos + +# A real build. Manual-only for now: there is no macOS runner, and Gitea leaves +# a job queued (not skipped) when no runner has its label, even behind an `if`. +# Once an act_runner with the "macos" label is registered, add `push:` here. +on: + workflow_dispatch: + +jobs: + build: + runs-on: macos + steps: + - uses: actions/checkout@v4 + + - name: Check + run: scripts/check.sh + + - name: Build (warnings are errors) + run: swift build -c release -Xswiftc -warnings-as-errors + + - name: Bundle and ad-hoc sign + run: | + make bundle + plutil -lint dist/Wakeful.app/Contents/Info.plist diff --git a/Makefile b/Makefile index 228e97c..b09df91 100644 --- a/Makefile +++ b/Makefile @@ -15,7 +15,7 @@ CONTENTS := $(APP)/Contents # A secure timestamp is required for notarization but impossible for ad-hoc. TS := $(if $(filter -,$(SIGN_ID)),--timestamp=none,--timestamp) -.PHONY: all build universal app app-universal bundle sign install uninstall run clean +.PHONY: all build universal app app-universal bundle sign install uninstall run check clean all: app @@ -73,6 +73,10 @@ uninstall: run: build "$(BUILD_DIR)/$(APP_NAME)" +## The same basic checks CI runs (syntax, Info.plist, version, whitespace). +check: + scripts/check.sh + clean: swift package clean rm -rf .build "$(DIST)" diff --git a/README.md b/README.md index c9ed48d..73f85f5 100644 --- a/README.md +++ b/README.md @@ -142,6 +142,7 @@ Other targets: | `make app` | Build and sign into `./dist/Wakeful.app` without installing | | `make app-universal` | Same, from a universal arm64 + x86_64 binary | | `make run` | Run the raw binary in the terminal (for debugging) | +| `make check` | Run the basic checks CI runs on every push | | `make uninstall` | Quit, remove from `/Applications`, delete preferences | | `make clean` | Remove build artifacts | diff --git a/scripts/check.sh b/scripts/check.sh new file mode 100755 index 0000000..7c33e37 --- /dev/null +++ b/scripts/check.sh @@ -0,0 +1,51 @@ +#!/bin/sh +# Basic checks that need no macOS SDK, so CI can run them on Linux. +# Run locally with `make check`. A full compile still needs a Mac. +set -eu +cd "$(dirname "$0")/.." + +fail=0 +step() { printf '\n==> %s\n' "$1"; } +bad() { printf 'FAIL: %s\n' "$1"; fail=1; } + +step "Swift syntax" +swiftc -parse Sources/Wakeful/*.swift || bad "swiftc -parse reported errors" + +step "Info.plist" +# The Makefile seds these placeholders in; losing one ships a broken bundle. +for key in __BUNDLE_ID__ __VERSION__; do + grep -q "$key" Resources/Info.plist || bad "Resources/Info.plist is missing $key" +done +if command -v plutil >/dev/null 2>&1; then + plutil -lint Resources/Info.plist || bad "Resources/Info.plist is not a valid plist" +else + swift - <<'SWIFT' || bad "Resources/Info.plist is not a valid plist" +import Foundation +let data = try Data(contentsOf: URL(fileURLWithPath: "Resources/Info.plist")) +_ = try PropertyListSerialization.propertyList(from: data, format: nil) +print("Resources/Info.plist: OK") +SWIFT +fi + +step "Version" +version=$(sed -n 's/^VERSION *:= *//p' Makefile) +latest=$(sed -n 's/^## \[\([0-9][^]]*\)\].*/\1/p' CHANGELOG.md | head -n 1) +if [ -z "$version" ]; then + bad "no VERSION in Makefile" +elif [ "$version" != "$latest" ]; then + bad "Makefile VERSION is $version but the newest CHANGELOG release is ${latest:-missing}" +else + echo "$version" +fi + +step "Whitespace and conflict markers" +# Diff the whole tree against the empty tree so this works on a shallow clone. +empty=$(git hash-object -t tree /dev/null) +git diff --check "$empty" HEAD -- . && echo OK || bad "whitespace errors or conflict markers (see above)" + +echo +if [ "$fail" -ne 0 ]; then + echo "Checks failed." + exit 1 +fi +echo "All checks passed."