Skip to content

fix(release): do not vendor static archives as loadable plug-ins #76

fix(release): do not vendor static archives as loadable plug-ins

fix(release): do not vendor static archives as loadable plug-ins #76

Workflow file for this run

name: release
on:
push:
tags:
- 'v*'
workflow_dispatch:
permissions:
contents: read
env:
RUST_LOG: info
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: true
jobs:
# ── Linux ─────────────────────────────────────────────────────────────
build-linux:
name: build Linux ${{ matrix.arch }}
strategy:
fail-fast: false
matrix:
include:
- arch: x86_64
runner: ubuntu-22.04
target: x86_64-unknown-linux-gnu
- arch: aarch64
runner: ubuntu-22.04-arm
target: aarch64-unknown-linux-gnu
runs-on: ${{ matrix.runner }}
timeout-minutes: 120
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
TMPDIR: ${{ github.workspace }}/tmp
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- name: Install system dependencies (apt)
run: scripts/ci/setup-linux.sh release
- name: Prepare workspace temp directory
run: mkdir -p "$TMPDIR"
- name: Set up Rust tooling
uses: ./.github/actions/setup-rust
with:
cache-key: release-linux-${{ matrix.arch }}
target: ${{ matrix.target }}
- name: Install linuxdeploy
uses: AnimMouse/setup-appimage@c8debfb91e47b03bf4bdc78a4b22fe14ebfaf7f5 # v2
with:
filename: linuxdeploy
url: https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-${{ matrix.arch }}.AppImage
cache_key: continuous-${{ matrix.arch }}
- name: Install appimagetool
uses: AnimMouse/setup-appimage@c8debfb91e47b03bf4bdc78a4b22fe14ebfaf7f5 # v2
with:
filename: appimagetool
url: https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-${{ matrix.arch }}.AppImage
cache_key: continuous-${{ matrix.arch }}
- name: Build release binaries
run: cargo xtask fresh-build --release --features neomacs-layout-engine/freetype-bundled
- name: Set version env
run: echo "VERSION=${GITHUB_REF_NAME#v}" >> "$GITHUB_ENV"
- name: Package tarball
run: |
# package-release.sh builds the canonical release tree
# (bin/ + share/neomacs/) and its tarball: the archive is runnable
# in place and install.sh consumes the same layout.
./scripts/package-release.sh \
--target "${{ matrix.target }}" \
--skip-build --no-smoke
- name: Package AppImage
run: |
./scripts/package-appimage.sh \
--target "${{ matrix.target }}" \
--skip-build --no-smoke
- name: Package .deb
run: |
./scripts/package-deb.sh \
--target "${{ matrix.target }}" \
--skip-build --no-smoke
- name: Package .rpm
run: |
./scripts/package-rpm.sh \
--target "${{ matrix.target }}" \
--skip-build --no-smoke
- name: Verify packaged artifacts and GLIBC baseline
run: |
./scripts/test-linux-release-artifacts.sh \
--target "${{ matrix.target }}" \
--tar-version "$VERSION"
- name: Upload Linux artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: linux-${{ matrix.arch }}
path: |
dist/*.tar.gz
dist/*.AppImage
dist/*.deb
dist/*.rpm
if-no-files-found: error
# ── macOS aarch64 (Apple Silicon) ─────────────────────────────────────
build-macos-aarch64:
name: build macOS aarch64
# Pin the image so a release does not change underneath us when GitHub
# advances `macos-latest`. GitHub's standard macos-15 image is arm64.
runs-on: macos-15
timeout-minutes: 180
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
GSTREAMER_VERSION: 1.26.9
# vendor-macos-runtime.sh rewrites every non-system load command to
# @executable_path/../Frameworks/..., which is LONGER than the original
# install name. Mach-O has no room to grow those load commands unless
# the linker reserved it, so install_name_tool failed on mock-display
# with "larger updated load commands do not fit (the program must be
# relinked, and you may need to use -headerpad_max_install_names)".
# Reserve the space for every binary that goes into the bundle -- the
# others only fit by accident of their existing padding.
RUSTFLAGS: "-C link-arg=-Wl,-headerpad_max_install_names"
outputs:
distribution_mode: ${{ steps.macos-signing.outputs.distribution_mode }}
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- name: Restore GStreamer SDK package cache
id: cache-macos-gstreamer
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: tmp/gstreamer-macos
key: macos-gstreamer-${{ env.GSTREAMER_VERSION }}-v1
- name: Install pinned GStreamer SDK
run: ./scripts/setup-macos-gstreamer.sh
- name: Save GStreamer SDK package cache
if: steps.cache-macos-gstreamer.outputs.cache-hit != 'true'
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: tmp/gstreamer-macos
key: macos-gstreamer-${{ env.GSTREAMER_VERSION }}-v1
- name: Set up Rust tooling
uses: ./.github/actions/setup-rust
with:
cache-key: release-macos-aarch64
target: aarch64-apple-darwin
- name: Set version env
run: echo "VERSION=${GITHUB_REF_NAME#v}" >> "$GITHUB_ENV"
- name: Build release binaries
# fresh-build EXECUTES neomacs-temacs to produce the dump, and that
# happens before the .app exists, so vendor-macos-runtime.sh cannot
# help it. setup-macos-gstreamer.sh exports only PKG_CONFIG_PATH,
# which the linker uses, so temacs died with
# `dyld: Library not loaded: @rpath/libgstvideo-1.0.0.dylib`.
# Build-time only: it changes no load command, so the shipped bundle
# is still whatever vendor-macos-runtime.sh makes it. Set inline
# rather than via $GITHUB_ENV because SIP strips DYLD_* when /bin/bash
# (the shell of every run: step) is exec'd; bash then execs cargo,
# which is not protected, so it reaches temacs.
run: |
gst_libdir="$(pkg-config --variable=libdir gstreamer-1.0)"
test -n "$gst_libdir" || { echo "gstreamer-1.0 has no libdir" >&2; exit 1; }
DYLD_FALLBACK_LIBRARY_PATH="$gst_libdir${DYLD_FALLBACK_LIBRARY_PATH:+:$DYLD_FALLBACK_LIBRARY_PATH}" \
cargo xtask fresh-build --release --features neomacs-layout-engine/freetype-bundled
- name: Configure Developer ID signing and notarization
id: macos-signing
env:
# Developer ID is an optional trust upgrade. Set this repository
# variable to 1 only after all five secrets below are configured.
MACOS_REQUIRE_SIGNING: ${{ vars.MACOS_REQUIRE_SIGNING || '0' }}
MACOS_CERTIFICATE_P12_BASE64: ${{ secrets.MACOS_CERTIFICATE_P12_BASE64 }}
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
APPLE_NOTARY_KEY_P8_BASE64: ${{ secrets.APPLE_NOTARY_KEY_P8_BASE64 }}
APPLE_NOTARY_KEY_ID: ${{ secrets.APPLE_NOTARY_KEY_ID }}
APPLE_NOTARY_ISSUER_ID: ${{ secrets.APPLE_NOTARY_ISSUER_ID }}
run: ./scripts/configure-macos-signing-ci.sh
- name: Build and package complete macOS application
run: ./scripts/package-macos-app.sh --skip-build
- name: Upload notarization diagnostics
if: failure()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: macos-notarization-diagnostics
path: dist/notary/*.json
if-no-files-found: ignore
- name: Upload macOS aarch64 artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: macos-aarch64
path: |
dist/*.dmg
dist/*.zip
dist/*.tar.gz
if-no-files-found: error
# Test on a second, clean machine so the SDK installed by the build cannot
# satisfy an accidentally external dependency or runtime resource lookup.
verify-macos-aarch64:
name: verify macOS aarch64 distribution
needs: build-macos-aarch64
runs-on: macos-15
timeout-minutes: 60
env:
# Verify Gatekeeper/notary state only when the build actually used a
# Developer ID. Ad-hoc releases still receive signature, dependency,
# resource, container-integrity, and clean-machine launch verification.
MACOS_DISTRIBUTION_MODE: ${{ needs.build-macos-aarch64.outputs.distribution_mode }}
steps:
- name: Checkout verification scripts outside the build-time source path
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
path: verification-source
- name: Download the distributed macOS artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: macos-aarch64
path: verification-source/dist
- name: Verify every distributed macOS artifact
working-directory: verification-source
run: |
./scripts/test-macos-release-artifact.sh \
dist/*.dmg \
dist/*.zip \
dist/*.tar.gz
- name: Verify install.sh from the published release
run: |
# Same user path as the Linux verify-install-script job: fetch the
# installer asset from the just-published release and let its own
# post-install check batch-start the app bundle's binary through
# the ~/.local/bin symlink (no environment variables).
curl -fsSL \
"https://github.com/eval-exec/neomacs/releases/download/${GITHUB_REF_NAME}/install.sh" \
-o install.sh
sh install.sh --tag "${GITHUB_REF_NAME}"
test -x "$HOME/.local/bin/neomacs"
env -u NEOMACS_RUNTIME_ROOT \
"$HOME/.local/bin/neomacs" --batch --eval '(princ (emacs-version))'
# ── Windows ───────────────────────────────────────────────────────────
build-windows:
name: build Windows ${{ matrix.arch }}
strategy:
fail-fast: false
matrix:
include:
- arch: x86_64
runner: windows-latest
target: x86_64-pc-windows-msvc
gstreamer_arch: x86_64
- arch: aarch64
runner: windows-11-arm
target: aarch64-pc-windows-msvc
gstreamer_arch: arm64
runs-on: ${{ matrix.runner }}
timeout-minutes: 180
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
GSTREAMER_VERSION: 1.28.6
GSTREAMER_ARCH: ${{ matrix.gstreamer_arch }}
NEOMACS_RELEASE_TARGET: ${{ matrix.target }}
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- name: Restore GStreamer SDK cache
id: cache-gstreamer
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
C:\gstreamer
C:\gstreamer-installer-cache
key: windows-gstreamer-msvc-${{ matrix.gstreamer_arch }}-${{ env.GSTREAMER_VERSION }}-v1
- name: Install GStreamer SDK
if: steps.cache-gstreamer.outputs.cache-hit != 'true'
shell: powershell
run: ./scripts/setup-windows-gstreamer.ps1 -Install
- name: Save GStreamer SDK cache
if: steps.cache-gstreamer.outputs.cache-hit != 'true'
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
C:\gstreamer
C:\gstreamer-installer-cache
key: ${{ steps.cache-gstreamer.outputs.cache-primary-key }}
- name: Configure GStreamer SDK
shell: powershell
run: ./scripts/setup-windows-gstreamer.ps1
- name: Set up Rust tooling
uses: ./.github/actions/setup-rust
with:
cache-key: release-windows-msvc-${{ matrix.arch }}
target: ${{ matrix.target }}
- name: Install NSIS
run: choco install nsis -y
- name: Add NSIS to PATH
shell: powershell
run: Add-Content -Path $env:GITHUB_PATH -Value "C:\Program Files (x86)\NSIS"
- name: Verify GStreamer pkg-config
shell: bash
run: source scripts/windows-gstreamer-env.sh --verify
- name: Build release binaries
shell: bash
timeout-minutes: 150
run: |
source scripts/windows-gstreamer-env.sh
# The pdump generation runs neomacs, whose startup expands `~`. The
# minimal CI build environment sets USERPROFILE/APPDATA but not HOME,
# so `~` would stay literal and `directory-files "~"` would fail. Give
# it HOME explicitly (a real Windows session always has one; GNU's
# w32.c init_environment likewise guarantees HOME is set).
export HOME="${USERPROFILE:-$GITHUB_WORKSPACE}"
cargo xtask fresh-build --release --features neomacs-layout-engine/freetype-bundled
- name: Set version env (bash)
shell: bash
run: echo "VERSION=${GITHUB_REF_NAME#v}" >> "$GITHUB_ENV"
- name: Package zip
shell: bash
run: |
TARGET="$NEOMACS_RELEASE_TARGET"
STAGING="neomacs-${VERSION}-${TARGET}"
mkdir -p "$STAGING"
cp target/release/neomacs.exe "$STAGING/"
cp target/release/neomacsclient.exe "$STAGING/"
cp target/release/neomacs.pdump "$STAGING/"
cp -r lisp "$STAGING/"
cp -r etc "$STAGING/"
cp COPYING "$STAGING/"
./scripts/vendor-windows-gstreamer-runtime.sh \
--package-root "$STAGING" \
--bin-dir "$STAGING"
mkdir -p dist
7z a -tzip "dist/${STAGING}.zip" "$STAGING"
- name: Package .exe installer
shell: bash
timeout-minutes: 20
run: |
source scripts/windows-gstreamer-env.sh
./scripts/package-windows-installer.sh \
--target "$NEOMACS_RELEASE_TARGET" \
--skip-build --no-smoke
- name: Verify Windows installer ownership contract
shell: powershell
timeout-minutes: 10
run: |
./scripts/run-windows-installer-contract.ps1 `
-Architecture "${{ matrix.arch }}" `
-ConfirmEphemeralRunner
- name: Upload Windows artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: windows-${{ matrix.arch }}
path: |
dist/*.zip
dist/*.exe
if-no-files-found: error
# ── GitHub Release ────────────────────────────────────────────────────
create-release:
name: create release
permissions:
contents: write
discussions: write
needs:
- build-linux
- build-macos-aarch64
- verify-macos-aarch64
- build-windows
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Download all artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
path: dist
merge-multiple: true
- name: Stage the installer and its checksum manifest
run: |
# install.sh ships as a release asset so that
# https://github.com/eval-exec/neomacs/releases/latest/download/install.sh
# always serves the installer that matches the assets it downloads
# (the neomacs.org entry point fetches exactly that URL).
install -m 0644 install.sh dist/install.sh
# The glob expands before SHA256SUMS exists, so the manifest covers
# every asset (and the installer) but never itself.
cd dist
sha256sum ./* > SHA256SUMS
- name: Create GitHub Release
uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2
with:
name: NEO Emacs ${{ github.ref_name }}
files: dist/*
generate_release_notes: true
discussion_category_name: Announcements
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# ── Install script verification ───────────────────────────────────────
# Installs through the public curl|bash entry exactly as a user would:
# the installer asset is downloaded from the just-published release, and
# its own post-install check batch-starts the binary with no environment
# variables, exercising the runtime-root resolution through the
# ~/.local/bin symlink chain.
verify-install-script:
name: verify install.sh from the published release
needs: create-release
runs-on: ubuntu-24.04
timeout-minutes: 20
env:
prefix: ${{ github.workspace }}/tmp/prefix
steps:
- name: Run the published installer
run: |
curl -fsSL \
"https://github.com/eval-exec/neomacs/releases/download/${GITHUB_REF_NAME}/install.sh" \
-o install.sh
sh install.sh --tag "${GITHUB_REF_NAME}" --prefix "$prefix"
- name: Assert the installed layout
run: |
version="${GITHUB_REF_NAME#v}"
test -x "$prefix/bin/neomacs"
test "$(readlink "$prefix/share/neomacs/current")" = "versions/$version"
test "$(readlink "$prefix/bin/neomacs")" = "../share/neomacs/current/bin/neomacs"
exe="$(readlink -f "$prefix/bin/neomacs")"
test -f "$(dirname "$exe")/neomacs.pdump"
test -d "$(dirname "$exe")/../share/neomacs/lisp"
env -u NEOMACS_RUNTIME_ROOT \
"$prefix/bin/neomacs" --batch --eval '(princ (emacs-version))'