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
63 changes: 63 additions & 0 deletions .github/workflows/package-static.yml
Original file line number Diff line number Diff line change
Expand Up @@ -180,13 +180,74 @@ jobs:
path: dist/windows-amd64/yiipress.exe
if-no-files-found: error

macos:
name: macOS static binary
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
runs-on: macos-latest
permissions:
contents: read

steps:
- name: Checkout repository
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
with:
persist-credentials: false

- name: Set up PHP
uses: shivammathur/setup-php@7c071dfe9dc99bdf297fa79cb49ea005b9fcadbc
with:
php-version: '8.5'
coverage: none

- name: Set up Rust
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8
with:
targets: aarch64-apple-darwin

- name: Install macOS package dependencies
run: |
brew install autoconf automake bison re2c pkgconf
echo "$(brew --prefix bison)/bin" >> "$GITHUB_PATH"

- name: Cache macOS package dependencies
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830
with:
path: |
~/.composer/cache/files
~/.cargo/git
~/.cargo/registry
runtime/package-macos/app/vendor
runtime/package-macos/static-php-cli
runtime/package-macos/md4c-extension
runtime/package-macos/yiipress-highlighter
key: macos-package-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('composer.lock', 'build/package-macos.sh', 'build/static-php/*.php') }}
restore-keys: |
macos-package-${{ runner.os }}-${{ runner.arch }}-

- name: Build macOS artifacts
run: make package-macos PACKAGE_MACOS_ARCH=arm64 PACKAGE_MACOS_DIST=dist/macos-arm64

- name: Smoke test macOS binary
run: ./dist/macos-arm64/yiipress --help

- name: Pack macOS artifact
run: tar -C dist/macos-arm64 -czf dist/yiipress-macos-arm64.tar.gz yiipress

- name: Upload macOS artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
name: yiipress-macos-arm64
path: dist/yiipress-macos-arm64.tar.gz
if-no-files-found: error
Comment thread
samdark marked this conversation as resolved.

release:
name: Publish release artifacts
if: startsWith(github.ref, 'refs/tags/')
needs:
- phar
- linux
- windows
- macos
runs-on: ubuntu-latest
permissions:
contents: write
Expand All @@ -200,6 +261,7 @@ jobs:
- name: Pack release archives
run: |
tar -C release/yiipress-linux-amd64 -czf yiipress-linux-amd64.tar.gz .
cp release/yiipress-macos-arm64/yiipress-macos-arm64.tar.gz yiipress-macos-arm64.tar.gz
zip -j yiipress-windows-amd64.zip release/yiipress-windows-amd64/*
cp release/yiipress-phar/yiipress.phar yiipress.phar

Expand All @@ -209,4 +271,5 @@ jobs:
files: |
yiipress.phar
yiipress-linux-amd64.tar.gz
yiipress-macos-arm64.tar.gz
yiipress-windows-amd64.zip
11 changes: 10 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,15 @@ DOCKER_COMPOSE_DEV := docker compose -f docker/compose.yml -f docker/dev/compose
DOCKER_COMPOSE_TEST := docker compose -f docker/compose.yml -f docker/test/compose.yml
PACKAGE_PLATFORM ?= linux/amd64
PACKAGE_LINUX_DIST ?= dist/linux-amd64
PACKAGE_MACOS_ARCH ?= $(shell uname -m | sed -e 's/x86_64/amd64/' -e 's/aarch64/arm64/')
PACKAGE_MACOS_DIST ?= dist/macos-$(PACKAGE_MACOS_ARCH)
PACKAGE_WINDOWS_DIST ?= dist/windows-amd64
PACKAGE_PHAR_DIST ?= dist/phar
PACKAGE_IMAGE ?= ${IMAGE}-static
MACOS_PACKAGE_ARGS ?=
WINDOWS_PACKAGE_ARGS ?=

.PHONY: build up open down stop clear shell yii composer rector cs-fix test test-coverage psalm composer-dependency-analyser bench-generate bench-generate-realistic bench bench-baseline bench-compare bench-profile profile-build php build-docs package package-phar package-linux package-windows package-distroless package-distroless-push prod-build prod-push prod-deploy help
.PHONY: build up open down stop clear shell yii composer rector cs-fix test test-coverage psalm composer-dependency-analyser bench-generate bench-generate-realistic bench bench-baseline bench-compare bench-profile profile-build php build-docs package package-phar package-linux package-macos package-windows package-distroless package-distroless-push prod-build prod-push prod-deploy help

#
# Development
Expand Down Expand Up @@ -214,6 +217,12 @@ package-linux: ## Build Linux static binary into dist/linux-amd64/
docker buildx build --file docker/Dockerfile --target package-linux-artifacts --platform $(PACKAGE_PLATFORM) --output type=local,dest=$(PACKAGE_LINUX_DIST) .
endif

ifeq ($(PRIMARY_GOAL),package-macos)
package-macos: ## Build macOS static executable into dist/macos-<arch>/
@command -v bash >/dev/null 2>&1 || { echo 'Bash is required for package-macos.' >&2; exit 127; }
build/package-macos.sh --dist-dir $(PACKAGE_MACOS_DIST) --arch $(PACKAGE_MACOS_ARCH) $(MACOS_PACKAGE_ARGS)
endif

ifeq ($(PRIMARY_GOAL),package-windows)
package-windows: ## Build Windows static executable into dist/windows-amd64/
@command -v pwsh >/dev/null 2>&1 || { echo 'PowerShell 7 (pwsh) is required for package-windows.' >&2; exit 127; }
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ or a database on the machine that builds or previews the site.

## Features

- Static Linux and Windows binaries for simple installs with no PHP runtime.
- Static Linux, macOS, and Windows binaries for simple installs with no PHP runtime.
- Docker images for CI and container-based workflows.
- Incremental and parallel builds with native Markdown, YAML, and syntax highlighting.
- Plain Markdown/YAML content that works well with Git.
Expand Down Expand Up @@ -127,6 +127,7 @@ Engine packaging commands, used by maintainers:
```shell
make package-phar
make package
make package-macos
make package-distroless
```

Expand Down
206 changes: 206 additions & 0 deletions build/package-macos.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
#!/usr/bin/env bash

set -euo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
PHP_VERSION="${PHP_VERSION:-8.5}"
MD4C_VERSION="${MD4C_VERSION:-1.1}"
HIGHLIGHTER_VERSION="${HIGHLIGHTER_VERSION:-1.0.1}"
STATIC_PHP_CLI_REF="${STATIC_PHP_CLI_REF:-5b5861c366a0d94bc84002db7b3f46144b388fbb}"
STATIC_PHP_EXTENSIONS="${STATIC_PHP_EXTENSIONS:-ctype,dom,filter,highlighter,mbstring,md4c,opcache,pcntl,phar,posix,xml,xmlwriter,yaml}"

detect_arch() {
case "$(uname -m)" in
arm64|aarch64)
printf 'arm64'
;;
x86_64|amd64)
printf 'amd64'
;;
*)
printf 'Unsupported macOS architecture: %s\n' "$(uname -m)" >&2
exit 1
;;
esac
}

HOST_ARCH="$(detect_arch)"
ARCH="$HOST_ARCH"
DIST_DIR="dist/macos-${ARCH}"

while [ "$#" -gt 0 ]; do
case "$1" in
--arch)
ARCH="$2"
shift 2
;;
--dist-dir)
DIST_DIR="$2"
shift 2
;;
*)
printf 'Unknown argument: %s\n' "$1" >&2
exit 1
;;
esac
done

if [ "$(uname -s)" != "Darwin" ]; then
printf 'package-macos must run on macOS.\n' >&2
exit 1
fi

if [ "$ARCH" != "$HOST_ARCH" ]; then
printf 'package-macos does not support cross-compilation: requested %s on %s host.\n' "$ARCH" "$HOST_ARCH" >&2
exit 1
fi

case "$ARCH" in
arm64)
CARGO_BUILD_TARGET="aarch64-apple-darwin"
;;
amd64)
CARGO_BUILD_TARGET="x86_64-apple-darwin"
;;
Comment thread
samdark marked this conversation as resolved.
*)
printf 'Unsupported macOS package architecture: %s\n' "$ARCH" >&2
exit 1
;;
esac

DIST_PATH="${ROOT}/${DIST_DIR}"
WORK_PATH="${ROOT}/runtime/package-macos"
APP_PATH="${WORK_PATH}/app"
STATIC_PHP_PATH="${WORK_PATH}/static-php-cli"
MD4C_PATH="${WORK_PATH}/md4c-extension"
HIGHLIGHTER_PATH="${WORK_PATH}/yiipress-highlighter"
PHAR_PATH="${WORK_PATH}/yiipress.phar"
BIN_PATH="${DIST_PATH}/yiipress"

invoke() {
"$@"
}

require_command() {
if ! command -v "$1" >/dev/null 2>&1; then
printf 'Required command was not found in PATH: %s\n' "$1" >&2
exit 127
fi
}

write_log_tail() {
local path="$1"
if [ -f "$path" ]; then
printf '===== %s =====\n' "$path"
tail -n 200 "$path"
fi
}

expand_tar_gz_archive() {
local url="$1"
local destination="$2"
local archive

archive="$(mktemp "${WORK_PATH}/archive.XXXXXX")"
curl -fsSL --max-time 300 --retry 3 --retry-delay 5 "$url" -o "$archive"
mkdir -p "$destination"
tar -xzf "$archive" --strip-components=1 -C "$destination"
rm -f "$archive"
}

copy_clean_path() {
local source="$1"
local destination="$2"

rm -rf "$destination"
cp -R "$source" "$destination"
}

for command in php composer tar curl rustup cargo make; do
require_command "$command"
done

mkdir -p "$DIST_PATH" "$WORK_PATH"
rm -f "${DIST_PATH}/yiipress.phar"

mkdir -p "$APP_PATH"
for directory in config public src themes; do
copy_clean_path "${ROOT}/${directory}" "${APP_PATH}/${directory}"
done
mkdir -p "${APP_PATH}/build"
cp "${ROOT}/build/package-phar.php" "${APP_PATH}/build/package-phar.php"
cp "${ROOT}/build/PharArchiveFilter.php" "${APP_PATH}/build/PharArchiveFilter.php"
cp "${ROOT}/yii" "${APP_PATH}/yii"
cp "${ROOT}/composer.json" "${APP_PATH}/composer.json"
cp "${ROOT}/composer.lock" "${APP_PATH}/composer.lock"

pushd "$APP_PATH" >/dev/null
invoke composer install \
--no-dev \
--no-progress \
--no-interaction \
--classmap-authoritative \
--ignore-platform-req=ext-inotify \
--ignore-platform-req=ext-md4c \
--ignore-platform-req=ext-yaml \
--ignore-platform-req=ext-highlighter
invoke php -d phar.readonly=0 build/package-phar.php "$PHAR_PATH"
popd >/dev/null

if [ ! -d "$STATIC_PHP_PATH" ]; then
expand_tar_gz_archive \
"https://github.com/crazywhalecc/static-php-cli/archive/${STATIC_PHP_CLI_REF}.tar.gz" \
"$STATIC_PHP_PATH"
fi

if [ ! -d "$MD4C_PATH" ]; then
expand_tar_gz_archive "https://pecl.php.net/get/md4c-${MD4C_VERSION}.tgz" "$MD4C_PATH"
fi

if [ ! -d "$HIGHLIGHTER_PATH" ]; then
invoke composer create-project \
--no-dev \
--no-progress \
--no-interaction \
yiipress/highlighter \
"$HIGHLIGHTER_PATH" \
"$HIGHLIGHTER_VERSION"
fi

pushd "$STATIC_PHP_PATH" >/dev/null
invoke composer install \
--no-dev \
--no-progress \
--no-interaction \
--classmap-authoritative

export CARGO_BUILD_TARGET
export HIGHLIGHTER_SOURCE="$HIGHLIGHTER_PATH"
export YIIPRESS_MD4C_SOURCE="$MD4C_PATH"
export YIIPRESS_STATIC_INCLUDE_PROCESS_EXTENSIONS=1

pushd "$HIGHLIGHTER_PATH" >/dev/null
invoke rustup target add "$CARGO_BUILD_TARGET"
invoke cargo build --release --target "$CARGO_BUILD_TARGET"
popd >/dev/null

invoke php "${ROOT}/build/static-php/patch-extension-config.php" config/ext.json
invoke php "${ROOT}/build/static-php/patch-source-config.php" config/source.json config/lib.json
invoke php bin/spc download \
--for-extensions="${STATIC_PHP_EXTENSIONS}" \
--with-php="${PHP_VERSION}" \
--without-suggestions
invoke php bin/spc doctor --auto-fix
if ! invoke php bin/spc build \
"$STATIC_PHP_EXTENSIONS" \
--build-micro \
-P "${ROOT}/build/static-php/register-yiipress-highlighter.php"; then
write_log_tail "${STATIC_PHP_PATH}/log/spc.output.log"
write_log_tail "${STATIC_PHP_PATH}/log/spc.shell.log"
exit 1
fi
invoke php bin/spc micro:combine "$PHAR_PATH" -O "$BIN_PATH"
popd >/dev/null

chmod +x "$BIN_PATH"
printf 'Built %s\n' "$BIN_PATH"
4 changes: 3 additions & 1 deletion build/static-php/register-yiipress-highlighter.php
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,9 @@
);

if ($highlighterWindowsConfigReplacementCount !== 1) {
throw new RuntimeException('Unable to patch highlighter config.w32 extension mode.');
if (!str_contains($highlighterWindowsConfigContents, 'EXTENSION("highlighter", "highlighter.c", false);')) {
throw new RuntimeException('Unable to patch highlighter config.w32 extension mode.');
}
}

file_put_contents($highlighterWindowsConfig, $highlighterWindowsConfigContents);
Expand Down
4 changes: 3 additions & 1 deletion docs/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,12 +160,14 @@ Additional package targets are available:
```bash
make package-phar
make package-linux
make package-macos
make package-windows
make package-distroless
make package-distroless-push
```

- `make package-linux` is the explicit Linux static executable target. Set `PACKAGE_PLATFORM=linux/amd64` and `PACKAGE_LINUX_DIST=...` to override the defaults.
- `make package-macos` builds the native macOS static executable into `dist/macos-<arch>/yiipress` with the shell packaging script. It is intended for macOS hosts with PHP, Composer, Rust, and the Xcode command-line build toolchain available. `PACKAGE_MACOS_ARCH` must match the host architecture (`arm64` or `amd64`); set `PACKAGE_MACOS_DIST=...` to override the output directory. The intermediate PHAR used by `static-php-cli` is kept under `runtime/package-macos/` and is not part of the macOS artifact.
- `make package-windows` builds only `dist/windows-amd64/yiipress.exe` with the PowerShell packaging script. It is intended for Windows hosts with PowerShell 7 (`pwsh`), PHP, Composer, Rust, and the Visual Studio C++ toolchain available. The intermediate PHAR used by `static-php-cli` is kept under `runtime/package-windows/` and is not part of the Windows artifact.
- `make package-phar` builds `dist/phar/yiipress.phar` separately for environments that already have PHP 8.5 and required extensions. Set `PACKAGE_PHAR_DIST=...` to override the output directory.
- `make package-distroless` builds a local `${IMAGE}-static:${IMAGE_TAG}` image from the `distroless` Docker target. The image copies only the static `yiipress` binary into a distroless base and runs it as the entrypoint.
Expand All @@ -176,4 +178,4 @@ The static executable includes `ext-highlighter`, so syntax highlighting does no
Relative `content-dir`, `output-dir`, `new`, `clean`, `serve`, and `import` paths are resolved from the directory where you run `yiipress`, not from the packaged executable location.
PHAR and static binary runs keep build cache and incremental manifests under the OS temp directory, keyed by the current project directory, instead of writing to `runtime/` in the site checkout. `yiipress clean` removes that packaged cache as well as the configured output directory.

GitHub Actions builds the same outputs in the `Package Static Builds` workflow. Commits to `master` publish separate nightly PHAR, Linux, and Windows workflow artifacts and push the distroless image as `ghcr.io/<owner>/<repo>-static:nightly` plus a commit-specific `nightly-<sha>` tag. Version tags publish the standalone `yiipress.phar`, Linux archive, and Windows archive to the GitHub release and push semver tags for the distroless image.
GitHub Actions builds the same outputs in the `Package Static Builds` workflow. Commits to `master` publish separate nightly PHAR, Linux, macOS, and Windows workflow artifacts and push the distroless image as `ghcr.io/<owner>/<repo>-static:nightly` plus a commit-specific `nightly-<sha>` tag. Version tags publish the standalone `yiipress.phar`, Linux archive, macOS archive, and Windows archive to the GitHub release and push semver tags for the distroless image.
2 changes: 1 addition & 1 deletion roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
- [x] Live reload / file watching during `yiipress serve`
- [x] PHAR and static PHP binary packaging
- [x] Worker-mode serving for PHAR and static PHP binary packages
- [x] Linux, Windows, and distroless Docker release packaging
- [x] Linux, macOS, Windows, and distroless Docker release packaging
- [x] `yiipress new` command — scaffold new entries from archetypes/templates
- [x] `yiipress init` command — scaffold initial content structure
- [x] Incremental builds (only rebuild changed files)
Expand Down
Loading
Loading