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
17 changes: 9 additions & 8 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,8 @@ jobs:
- name: Install build dependencies
run: pip install ".[build]"

- name: Build binary with PyInstaller
run: pyinstaller --onefile --name liminate --collect-all liminate build/entry.py

# The certificate comes before the build: PyInstaller signs what it
# collects with it (build/build_macos_release.sh says why).
- name: Import signing certificate
env:
APPLE_CERTIFICATE_P12: ${{ secrets.APPLE_CERTIFICATE_P12 }}
Expand Down Expand Up @@ -51,11 +50,8 @@ jobs:
fi
echo "SIGNING_IDENTITY=$IDENTITY" >> "$GITHUB_ENV"

- name: Codesign binary
run: |
codesign --force --options runtime --timestamp \
--sign "$SIGNING_IDENTITY" dist/liminate
codesign --verify --verbose dist/liminate
- name: Build, sign and run the binary
run: build/build_macos_release.sh

- name: Notarize binary
env:
Expand Down Expand Up @@ -185,8 +181,13 @@ jobs:
cp artifacts/liminate-windows-x64/liminate-windows-x64.exe release/
chmod +x release/liminate-macos-arm64 release/liminate-linux-x64

# An asset already on the release stays. When Actions can't run, the
# macOS binary is built and attached by hand (RELEASING.md), and
# downstream builds pin its SHA-256; a later run of this workflow fills
# in the rest without replacing it.
- uses: softprops/action-gh-release@v3
with:
overwrite_files: false
files: |
release/liminate-macos-arm64
release/liminate-linux-x64
Expand Down
32 changes: 32 additions & 0 deletions RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,38 @@ The binary lands at `dist/liminate`. The `--collect-all liminate` flag
is required by the src/ layout — without it PyInstaller misses
submodules like `packs/timer.py`.

The macOS release binary is built by `build/build_macos_release.sh`
instead, which the workflow also calls. It passes
`--codesign-identity`, so the Python.framework inside the one-file
binary is signed with the same team as the binary itself. Without that,
the hardened runtime's library validation refuses the framework and the
binary exits before running anything, which is what happened to
0.18.2's macOS asset. `codesign --verify` passes either way, so the
script also runs the built binary.

---

## When Actions can't run

The macOS asset can be released by hand with the same recipe:

```bash
python3.12 -m venv .venv-release && . .venv-release/bin/activate
pip install ".[build]"
SIGNING_IDENTITY="Developer ID Application: <Your Name> (<TEAM_ID>)" \
build/build_macos_release.sh
ditto -c -k dist/liminate liminate.zip
xcrun notarytool submit liminate.zip --keychain-profile <profile> --wait
mv dist/liminate dist/liminate-macos-arm64
git tag v0.x.x && git push origin v0.x.x
gh release create v0.x.x dist/liminate-macos-arm64 --title v0.x.x --notes "…"
```

When Actions runs again, re-run the tag's `Release` workflow. It builds
the Linux and Windows binaries, publishes to PyPI, and leaves the
macOS asset already on the release alone (`overwrite_files: false`),
so a SHA-256 pinned downstream stays valid.

---

## Homebrew tap (manual, future)
Expand Down
23 changes: 23 additions & 0 deletions build/build_macos_release.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/usr/bin/env bash
# The macOS release binary: build, sign, and run it. release.yml calls this,
# and so does a local release when Actions can't run, so both produce the
# binary the same way. Needs SIGNING_IDENTITY (a Developer ID Application
# identity) and the [build] extra installed. Notarization is a separate step.
set -euo pipefail
: "${SIGNING_IDENTITY:?set SIGNING_IDENTITY to a Developer ID Application identity}"

# --codesign-identity signs every binary PyInstaller collects with our
# identity, above all the Python.framework a one-file build unpacks at launch.
# Without it that framework keeps its original team's signature, and the
# hardened runtime's library validation refuses to load it into a process
# signed by ours: the binary exits before running a line. 0.18.2's did.
pyinstaller --noconfirm --onefile --name liminate --collect-all liminate \
--codesign-identity "$SIGNING_IDENTITY" build/entry.py

codesign --force --options runtime --timestamp --sign "$SIGNING_IDENTITY" dist/liminate
codesign --verify --strict --verbose dist/liminate

# Run it the way a user does. 0.18.2 passed codesign --verify and still
# could not start.
dist/liminate --version
dist/liminate --quiet --test examples/program1_basics.limn > /dev/null
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ where = ["src"]

[project]
name = "liminate"
version = "0.18.2"
version = "0.18.3"
description = "A prose-as-syntax language designed from the human end."
requires-python = ">=3.10"
license = "Apache-2.0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1579,6 +1579,6 @@
}
],
"generator": "scripts/gen_conformance_corpus.py",
"language_version": "0.18.2",
"language_version": "0.18.3",
"surface": "tokenize -> reorder -> parse -> analyze -> render"
}
Loading