Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c231b1bac | ||
|
|
06b2289ed3 | ||
|
|
341bcacafe | ||
|
|
813b064cc1 | ||
|
|
7e734fec66 | ||
|
|
3c027255c8 |
No files matched your search
@@ -95,3 +95,7 @@ jobs:
|
||||
- name: Bridge-integration test (HTTP/SSE round-trip + crash-recover)
|
||||
working-directory: examples/todo
|
||||
run: ./tests/integration.sh
|
||||
|
||||
- name: Bundled-mode supervisor integration test
|
||||
working-directory: examples/todo
|
||||
run: make integration-bundled
|
||||
+20
-1
@@ -10,6 +10,24 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
|
||||
|
||||
- (none yet — next changes land here)
|
||||
|
||||
## [0.1.1] — 2026-05-03
|
||||
|
||||
Bugfix release closing the four follow-ups identified during the v0.1.0 shakedown. No new public API surface; `/healthz` response gains an additive `bundle` field (existing JSON consumers ignore unknown keys).
|
||||
|
||||
### Fixed
|
||||
|
||||
- **`HealthController` deep-loads the bundle.** Constructor-injects `Publisher` so `/healthz` returns 200 only when `BridgeBundle` is fully container-resolvable. v0.1.0's `/healthz` returned 200 against half-loaded bundles — both the path-repo symlink dangling at runtime and the read-only-cache failure shipped green through perfsmoke as a result. Response body now includes `bundle: "PhpQml\\Bridge\\Publisher"` as the canary value.
|
||||
- **Caddyfile formatting.** `framework/skeleton/Caddyfile` and `examples/todo/Caddyfile` reformatted with `caddy fmt`. The "Caddyfile input is not formatted; run 'caddy fmt --overwrite'" warning that fired on every FrankenPHP boot is gone.
|
||||
|
||||
### Added
|
||||
|
||||
- **Bundled-mode supervisor integration test** (`examples/todo/tests/bundled-supervisor.sh`, `make integration-bundled`). Stages a fake AppImage layout in `/tmp` (host binary copied — Qt's `applicationDirPath()` dereferences symlinks via `/proc/self/exe`, so the real layout has to be mimicked closely; staged Symfony tree is `chmod -R a-w` to actually exercise the read-only-mount cache redirect) and exercises the supervisor end-to-end without needing a real `.AppImage` build. Asserts `/healthz` deep-load + cache redirect. Wired into `.gitea/workflows/ci.yml` after the existing dev-mode integration test.
|
||||
- **Skeleton AppImage parity.** `framework/skeleton/Makefile` gains `staging-symfony` + `appimage` targets mirroring `examples/todo/Makefile`'s. New `framework/skeleton/packaging/skeleton.{desktop,png}` provide minimal AppImage assembly inputs. `bin/php-qml-init` now: (a) renames packaging files to match the scaffolded app name, (b) rewrites the `.desktop` file's `Name`/`Exec`/`Icon`, (c) substitutes the new `BUNDLE_SRC` and `PACKAGING` Makefile variables to either absolute framework paths (default) or vendored `.bridge` / `.bridge-packaging` paths (`--vendor`). Scaffolded apps inherit `make appimage` working out of the box.
|
||||
|
||||
### Notes
|
||||
|
||||
- `BackendConnection::m_port` stays hardcoded to 8765 — port-collision between two installed php-qml apps is a real bug surfaced during v0.1.1 prep, but the fix touches every consumer that hardcodes 8765 (perfsmoke, the new bundled-supervisor test) so it's tracked as a v0.2.0 item rather than a v0.1.x bugfix.
|
||||
|
||||
## [0.1.0] — 2026-05-03
|
||||
|
||||
First public preview. Phases 0 through 4a in PLAN.md are complete plus the Phase 5 DX-polish sub-commits. Linux is the only packaged target; macOS and Windows are deferred to 4b / 4c. Tagging is the user's call (release CI runs on `v*` tags).
|
||||
@@ -43,5 +61,6 @@ First public preview. Phases 0 through 4a in PLAN.md are complete plus the Phase
|
||||
- The bundle ships without `composer.lock` (it's a library); the skeleton and the todo example carry their own.
|
||||
- Licensed under **LGPL-3.0-or-later** (`LICENSE` + `LICENSE.GPL` at the repo root). Chosen to align with Qt 6's LGPLv3 licensing — see PLAN.md §12 for the relinkability obligations the AppImage build already honours.
|
||||
|
||||
[Unreleased]: https://src.bundespruefstelle.ch/magdev/php-qml/compare/v0.1.0...HEAD
|
||||
[Unreleased]: https://src.bundespruefstelle.ch/magdev/php-qml/compare/v0.1.1...HEAD
|
||||
[0.1.1]: https://src.bundespruefstelle.ch/magdev/php-qml/releases/tag/v0.1.1
|
||||
[0.1.0]: https://src.bundespruefstelle.ch/magdev/php-qml/releases/tag/v0.1.0
|
||||
@@ -1,6 +1,6 @@
|
||||
# php-qml — Plan for a Symfony/FrankenPHP + Qt/QML Desktop Framework
|
||||
|
||||
> **Status (2026-05):** v0.1.0 shipped 2026-05-03 (LGPL-3.0-or-later). Linux AppImage runs end-to-end. Planning is now version-based — see §13.
|
||||
> **Status (2026-05):** v0.1.0 shipped 2026-05-03 (LGPL-3.0-or-later). v0.1.1 ready to tag — closes the four shakedown follow-ups. Planning is version-based — see §13.
|
||||
>
|
||||
> **Where else to look:**
|
||||
>
|
||||
@@ -531,14 +531,14 @@ Per-phase scope detail is preserved in `CHANGELOG.md` (per-version summary) and
|
||||
|
||||
First public preview. Linux AppImage. Full entry in `CHANGELOG.md`; binaries at <https://src.bundespruefstelle.ch/magdev/php-qml/releases/tag/v0.1.0>.
|
||||
|
||||
### v0.1.1 — next bugfix
|
||||
### v0.1.1 — bugfix release (ready to tag)
|
||||
|
||||
Open follow-ups from v0.1.0 shakedown:
|
||||
All four shakedown follow-ups landed:
|
||||
|
||||
- **perfsmoke gap.** `/healthz` doesn't exercise any `BridgeBundle` services, so a broken bundle (the v0.1.0 path-repo-symlink + read-only-cache bugs both shipped green through perfsmoke). Extend perfsmoke to hit a real `#[BridgeResource]` API endpoint after the supervisor reports healthy.
|
||||
- **`bin/php-qml-init` parity.** Scaffolded apps copy `framework/skeleton/`, but the AppImage-relevant fixes (path-repo `symlink: false` sed, writable cache/log env-vars, `Kernel::getCacheDir/getLogDir` overrides) were applied to `examples/todo/`. Either move them into the skeleton (so init-scaffolded apps inherit them) or have `php-qml-init` apply them. Otherwise every scaffolded app re-discovers the same bugs.
|
||||
- **Bundled-mode integration test.** The current bridge-integration test only exercises dev mode (`BRIDGE_URL` set). The whole bundled supervisor codepath (`resolveFrankenphpBin`, `runMigrations`, `spawnChild`, supervisor restart) is only validated by perfsmoke against a real AppImage. A faked-AppImage-layout integration test would catch most of the v0.1.0 shakedown bugs in CI rather than in user reports.
|
||||
- **Caddyfile formatting.** FrankenPHP logs `Caddyfile input is not formatted; run 'caddy fmt --overwrite' to fix inconsistencies` on every boot. Cosmetic but noisy in the dev console.
|
||||
- **perfsmoke gap closed.** `HealthController` now constructor-injects `Publisher`; `/healthz` returns 200 only when `BridgeBundle` is fully container-resolvable. The `bundle` field in the response is the canary value perfsmoke + the bundled-mode integration test both check.
|
||||
- **Bundled-mode supervisor integration test.** `examples/todo/tests/bundled-supervisor.sh` (run via `make integration-bundled`) stages a fake AppImage layout in `/tmp` and exercises the whole supervisor codepath (`resolveFrankenphpBin` → `runMigrations` → `spawnChild` → cache/log redirect to user data dir) without needing a real `.AppImage` build. Wired into ci.yml. Catches every v0.1.0 shakedown bug.
|
||||
- **Skeleton AppImage parity.** `framework/skeleton/Makefile` gains `staging-symfony` + `appimage` targets mirroring the example's; `framework/skeleton/packaging/` ships starter `.desktop` + `.png`; `bin/php-qml-init` rewrites `BUNDLE_SRC` / `PACKAGING` Make variables and renames packaging files at scaffold time. `--vendor` mode also vendors `packaging/linux/` to `.bridge-packaging/`. Scaffolded apps inherit a working `make appimage` flow.
|
||||
- **Caddyfile fmt.** `framework/skeleton/Caddyfile` and `examples/todo/Caddyfile` reformatted per `caddy fmt`; the "Caddyfile input is not formatted" boot warning is gone.
|
||||
|
||||
### v0.2.0 — next minor
|
||||
|
||||
@@ -557,6 +557,7 @@ Pulls in the originally-Phase-3/5-deferred items that don't need new operational
|
||||
|
||||
**Operations (§12):**
|
||||
|
||||
- **Bundled-mode port negotiation.** `BackendConnection::m_port` is hardcoded to 8765 with no env override or negotiation, so two php-qml apps installed on the same machine collide on first launch (whichever loses the race goes Offline). Fix: bind a transient `QTcpServer` to `QHostAddress::LocalHost` port 0, grab `serverPort()`, hand it to FrankenPHP via the `PORT` env var. Needs a port-discovery mechanism for tests/perfsmoke that currently hardcode 8765 — likely write the chosen port to a sentinel file under the user data dir on supervisor activation. Surfaced from a v0.1.1 follow-up question; deferred to v0.2.0 because the test/consumer migration is wider than v0.1.x scope.
|
||||
- **Pre-migration auto-backup** (§12, *Migrations on schema change*). Supervisor copies `var/data.sqlite` to `var/data.sqlite.<timestamp>.bak` before invoking `doctrine:migrations:migrate`; trims to N most recent.
|
||||
- **`bridge:export` console command + UI hook** (§12, *Data backup / export*). Lets users copy their data out before machine moves or risky migrations.
|
||||
- **Periodic auto-update check.** Phase 5 noted this as a polish item but didn't ship; v0.1.0 only has menu-triggered manual checks.
|
||||
|
||||
+37
-1
@@ -146,11 +146,31 @@ sed -i \
|
||||
-e "s|php-qml — skeleton|php-qml — $NAME|g" \
|
||||
"$TARGET/qml/Main.qml"
|
||||
|
||||
# Makefile: $(BUILD_DIR)/skeleton → $(BUILD_DIR)/$NAME.
|
||||
# Makefile: rewrite identifiers for the appimage target — binary name,
|
||||
# packaging filenames, AppImage output filename. The path-repo + packaging
|
||||
# absolute paths are handled later (after we know vendor vs absolute mode).
|
||||
sed -i \
|
||||
-e "s|\$(BUILD_DIR)/skeleton|\$(BUILD_DIR)/$NAME|g" \
|
||||
-e "s|--app-name skeleton|--app-name $NAME|g" \
|
||||
-e "s|packaging/skeleton.desktop|packaging/$NAME.desktop|g" \
|
||||
-e "s|packaging/skeleton.png|packaging/$NAME.png|g" \
|
||||
-e "s|build/Skeleton-x86_64.AppImage|build/$PASCAL-x86_64.AppImage|g" \
|
||||
"$TARGET/Makefile"
|
||||
|
||||
# Rename packaging files to match the app name + rewrite the Exec/Icon
|
||||
# fields in the .desktop file (XDG-launched binary lookup uses these).
|
||||
if [ -f "$TARGET/packaging/skeleton.desktop" ]; then
|
||||
mv "$TARGET/packaging/skeleton.desktop" "$TARGET/packaging/$NAME.desktop"
|
||||
sed -i \
|
||||
-e "s|^Name=php-qml Skeleton|Name=php-qml $PASCAL|" \
|
||||
-e "s|^Exec=skeleton|Exec=$NAME|" \
|
||||
-e "s|^Icon=skeleton|Icon=$NAME|" \
|
||||
"$TARGET/packaging/$NAME.desktop"
|
||||
fi
|
||||
if [ -f "$TARGET/packaging/skeleton.png" ]; then
|
||||
mv "$TARGET/packaging/skeleton.png" "$TARGET/packaging/$NAME.png"
|
||||
fi
|
||||
|
||||
# .vscode/launch.json: binary path + config label both mention `skeleton`.
|
||||
if [ -f "$TARGET/.vscode/launch.json" ]; then
|
||||
sed -i \
|
||||
@@ -168,6 +188,8 @@ if [ "$VENDOR" -eq 1 ]; then
|
||||
mkdir -p "$TARGET/.bridge"
|
||||
say "vendoring framework/qml → $NAME/.bridge-qml/"
|
||||
mkdir -p "$TARGET/.bridge-qml"
|
||||
say "vendoring framework/packaging → $NAME/.bridge-packaging/"
|
||||
mkdir -p "$TARGET/.bridge-packaging"
|
||||
if command -v rsync >/dev/null 2>&1; then
|
||||
rsync -a --delete \
|
||||
--exclude 'vendor/' --exclude '.phpunit.cache/' \
|
||||
@@ -176,18 +198,23 @@ if [ "$VENDOR" -eq 1 ]; then
|
||||
rsync -a --delete \
|
||||
--exclude 'build/' \
|
||||
"$FRAMEWORK/framework/qml/" "$TARGET/.bridge-qml/"
|
||||
rsync -a --delete \
|
||||
"$FRAMEWORK/packaging/linux/" "$TARGET/.bridge-packaging/"
|
||||
else
|
||||
cp -R "$FRAMEWORK/framework/php/." "$TARGET/.bridge/"
|
||||
cp -R "$FRAMEWORK/framework/qml/." "$TARGET/.bridge-qml/"
|
||||
cp -R "$FRAMEWORK/packaging/linux/." "$TARGET/.bridge-packaging/"
|
||||
rm -rf "$TARGET/.bridge/vendor" "$TARGET/.bridge-qml/build" 2>/dev/null || true
|
||||
fi
|
||||
BUNDLE_URL="../.bridge"
|
||||
# qml/CMakeLists.txt lives at $TARGET/qml/, vendored qml module at
|
||||
# $TARGET/.bridge-qml/, so the relative path from the consumer is ../.bridge-qml.
|
||||
QML_FW_PATH="\${CMAKE_CURRENT_SOURCE_DIR}/../.bridge-qml"
|
||||
PACKAGING_PATH=".bridge-packaging"
|
||||
else
|
||||
BUNDLE_URL="$FRAMEWORK/framework/php"
|
||||
QML_FW_PATH="$FRAMEWORK/framework/qml"
|
||||
PACKAGING_PATH="$FRAMEWORK/packaging/linux"
|
||||
fi
|
||||
say "path-repo → $BUNDLE_URL"
|
||||
# Replace the original "../../php" path-repo URL. The skeleton's
|
||||
@@ -206,6 +233,15 @@ sed -i \
|
||||
-e "s|\${CMAKE_CURRENT_SOURCE_DIR}/../../qml|$QML_FW_PATH|g" \
|
||||
"$TARGET/qml/CMakeLists.txt"
|
||||
|
||||
# Makefile: BUNDLE_SRC + PACKAGING were framework-tree relative; rewrite
|
||||
# to absolute (or the vendored path). Both are matched against the literal
|
||||
# values in the skeleton Makefile.
|
||||
say "appimage paths → bundle=$BUNDLE_URL packaging=$PACKAGING_PATH"
|
||||
sed -i \
|
||||
-e "s|^BUNDLE_SRC := ../../php\$|BUNDLE_SRC := $BUNDLE_URL|" \
|
||||
-e "s|^PACKAGING := ../../packaging/linux\$|PACKAGING := $PACKAGING_PATH|" \
|
||||
"$TARGET/Makefile"
|
||||
|
||||
# ── Composer install + first-run migrations ──────────────────────────
|
||||
if [ "$SKIP_INSTALL" -eq 1 ]; then
|
||||
say "skipping composer install (--skip-install)"
|
||||
|
||||
@@ -61,6 +61,7 @@ bin/console make:bridge:resource Todo --int-id
|
||||
```
|
||||
|
||||
When to use which:
|
||||
|
||||
- **UUIDv7 (default)** — distributed-friendly, exposes no insertion-order leak, sortable. Good for anything an end-user might sync between machines.
|
||||
- **Integer ID** — easier to debug from a shell; smaller wire size; no client-side ID generation needed. Pick this for purely-local single-machine apps.
|
||||
|
||||
@@ -179,6 +180,7 @@ bin/console make:bridge:window Settings
|
||||
```
|
||||
|
||||
The generated QML window:
|
||||
|
||||
- Imports `PhpQml.Bridge`.
|
||||
- Wraps content in `AppShell` (so it shows the same Reconnecting / Offline chrome as the main window).
|
||||
- Has its own RestClient + a `Connections { target: SingleInstance ... }` block for launch-arg forwarding.
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
# - dev mode → env unset, defaults below match symfony/.env
|
||||
# - bundled mode → BackendConnection sets PORT and MERCURE_*_JWT_KEY
|
||||
# before launching FrankenPHP.
|
||||
|
||||
{
|
||||
auto_https off
|
||||
admin off
|
||||
|
||||
+11
-3
@@ -40,12 +40,16 @@ clean: ## Remove build artefacts
|
||||
integration: ## Run the bridge-integration test (FrankenPHP boot + HTTP/SSE round-trip + crash-recover)
|
||||
./tests/integration.sh
|
||||
|
||||
.PHONY: integration-bundled
|
||||
integration-bundled: build staging-symfony ## Bundled-mode integration test (faked AppImage layout, no .AppImage build needed)
|
||||
./tests/bundled-supervisor.sh
|
||||
|
||||
.PHONY: perf
|
||||
perf: ## Run the AppImage perf smoke test (PLAN.md §11 budgets)
|
||||
./tests/perfsmoke.sh build/Todo-x86_64.AppImage
|
||||
|
||||
.PHONY: appimage
|
||||
appimage: build ## Package as a single-file Linux AppImage at build/Todo-x86_64.AppImage
|
||||
.PHONY: staging-symfony
|
||||
staging-symfony: ## Stage a --no-dev composer copy of symfony for AppImage / bundled-mode tests
|
||||
# Composer install --no-dev in a staging copy of symfony so the
|
||||
# dev tree (with maker-bundle etc.) is left untouched.
|
||||
rm -rf build/staging-symfony
|
||||
@@ -62,6 +66,9 @@ appimage: build ## Package as a single-file Linux AppImage at build/Todo-x86_64.
|
||||
sed -i 's|"symlink": true|"symlink": false|' build/staging-symfony/composer.json
|
||||
rm -f build/staging-symfony/composer.lock
|
||||
cd build/staging-symfony && composer install --no-dev --no-interaction --classmap-authoritative
|
||||
|
||||
.PHONY: appimage
|
||||
appimage: build staging-symfony ## Package as a single-file Linux AppImage at build/Todo-x86_64.AppImage
|
||||
../../packaging/linux/build-appimage.sh \
|
||||
--app-name todo \
|
||||
--host-binary $(QT_BIN) \
|
||||
@@ -76,7 +83,8 @@ appimage: build ## Package as a single-file Linux AppImage at build/Todo-x86_64.
|
||||
@echo "AppImage built. Test with: ./build/Todo-x86_64.AppImage"
|
||||
|
||||
.PHONY: quality
|
||||
quality: build ## Run PHPStan, php-cs-fixer (check), PHPUnit, qmllint, integration
|
||||
quality: build ## Run PHPStan, php-cs-fixer (check), PHPUnit, qmllint, integration (dev + bundled)
|
||||
cd ../../framework/php && composer quality
|
||||
cmake --build $(BUILD_DIR) --target all_qmllint
|
||||
./tests/integration.sh
|
||||
$(MAKE) integration-bundled
|
||||
Executable
+141
@@ -0,0 +1,141 @@
|
||||
#!/usr/bin/env bash
|
||||
# Bundled-mode integration test (v0.1.1).
|
||||
#
|
||||
# Exercises the bundled-mode supervisor codepath end-to-end *without*
|
||||
# requiring a real AppImage build:
|
||||
#
|
||||
# - resolveFrankenphpBin (BackendConnection.cpp) — finds frankenphp
|
||||
# as a sibling of the host binary at usr/bin/frankenphp.
|
||||
# - resolveSymfonyDir / resolveCaddyfilePath — finds the staged
|
||||
# Symfony tree + Caddyfile under usr/share/<app>/.
|
||||
# - runMigrations + spawnChild — supervisor drives the doctrine
|
||||
# migrate, spawns frankenphp, polls /healthz.
|
||||
# - Kernel::getCacheDir / getLogDir override — Symfony writes to
|
||||
# the user data dir, not the (chmod -w) staged tree.
|
||||
# - HealthController deep-load — /healthz response includes a
|
||||
# `bundle` field proving BridgeBundle was autoloaded.
|
||||
#
|
||||
# Catches the v0.1.0 shakedown bugs (doubled bin/frankenphp path,
|
||||
# composer path-repo symlink dangling at runtime, read-only mount
|
||||
# var/cache failure) faster than perfsmoke against a real .AppImage.
|
||||
#
|
||||
# Designed for `make integration-bundled`. Expects the regular
|
||||
# `make build` artefacts to exist; runs `make staging-symfony`
|
||||
# itself if the staged tree isn't present.
|
||||
#
|
||||
# Skip-conditions:
|
||||
# - port 8765 already in use (don't trample a dev instance)
|
||||
# - frankenphp not on PATH
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
APP_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
BUILD_DIR="$APP_DIR/build/qml"
|
||||
HOST_BIN="$BUILD_DIR/todo"
|
||||
STAGING="$APP_DIR/build/staging-symfony"
|
||||
CADDYFILE="$APP_DIR/Caddyfile"
|
||||
|
||||
APP_NAME=todo
|
||||
PORT=8765
|
||||
|
||||
step() { echo "→ $*"; }
|
||||
fail() { echo "✗ FAIL: $*" >&2; exit 1; }
|
||||
skip() { echo "⊘ SKIP: $*" >&2; exit 0; }
|
||||
|
||||
# ── Pre-flight ─────────────────────────────────────────────────────────
|
||||
[ -x "$HOST_BIN" ] || fail "host binary not built — run 'make build' first ($HOST_BIN)"
|
||||
command -v frankenphp >/dev/null 2>&1 || skip "frankenphp not on PATH"
|
||||
[ -d "$STAGING" ] || { step "no staging-symfony, building it"; (cd "$APP_DIR" && make staging-symfony >/dev/null); }
|
||||
|
||||
if (echo > "/dev/tcp/127.0.0.1/$PORT") 2>/dev/null; then
|
||||
skip "port $PORT already in use (dev instance running?)"
|
||||
fi
|
||||
|
||||
# ── Stage a fake AppImage layout in a temp dir ─────────────────────────
|
||||
ROOT="$(mktemp -d)"
|
||||
DATA_DIR="$(mktemp -d)"
|
||||
trap 'cleanup' EXIT INT TERM
|
||||
|
||||
PID=""
|
||||
cleanup() {
|
||||
trap - EXIT INT TERM
|
||||
if [ -n "$PID" ] && kill -0 "$PID" 2>/dev/null; then
|
||||
kill -TERM "$PID" 2>/dev/null || true
|
||||
for _ in 1 2 3 4 5 6 7 8 9 10; do
|
||||
kill -0 "$PID" 2>/dev/null || break
|
||||
sleep 0.2
|
||||
done
|
||||
kill -KILL "$PID" 2>/dev/null || true
|
||||
fi
|
||||
# Restore writable so rm -rf doesn't choke.
|
||||
[ -d "$ROOT/usr/share/$APP_NAME/symfony" ] && \
|
||||
chmod -R u+w "$ROOT/usr/share/$APP_NAME/symfony" 2>/dev/null || true
|
||||
rm -rf "$ROOT" "$DATA_DIR"
|
||||
}
|
||||
|
||||
step "stage AppImage layout at $ROOT"
|
||||
mkdir -p "$ROOT/usr/bin" "$ROOT/usr/share/$APP_NAME"
|
||||
# Host binary must be copied, not symlinked: Qt's applicationDirPath()
|
||||
# reads /proc/self/exe which dereferences symlinks, so a symlinked host
|
||||
# would resolve to the build/ dir and the supervisor would look for
|
||||
# frankenphp + symfony there instead of in the staged layout. Real
|
||||
# AppImages copy the binary, mimicking that here.
|
||||
cp "$HOST_BIN" "$ROOT/usr/bin/$APP_NAME"
|
||||
ln -s "$(command -v frankenphp)" "$ROOT/usr/bin/frankenphp"
|
||||
cp -a "$STAGING/." "$ROOT/usr/share/$APP_NAME/symfony/"
|
||||
cp "$CADDYFILE" "$ROOT/usr/share/$APP_NAME/Caddyfile"
|
||||
|
||||
# Make the staged Symfony tree read-only so the cache/log redirect is
|
||||
# actually exercised — without the Kernel::getCacheDir/getLogDir override,
|
||||
# Symfony tries to mkdir var/cache here and fails.
|
||||
chmod -R a-w "$ROOT/usr/share/$APP_NAME/symfony"
|
||||
|
||||
# ── Launch the host ────────────────────────────────────────────────────
|
||||
step "launch host (bundled mode, offscreen, isolated XDG dirs)"
|
||||
LOG="$DATA_DIR/host.log"
|
||||
env -u BRIDGE_URL \
|
||||
XDG_DATA_HOME="$DATA_DIR/share" \
|
||||
XDG_CACHE_HOME="$DATA_DIR/cache" \
|
||||
XDG_CONFIG_HOME="$DATA_DIR/config" \
|
||||
QT_QPA_PLATFORM=offscreen \
|
||||
"$ROOT/usr/bin/$APP_NAME" > "$LOG" 2>&1 &
|
||||
PID=$!
|
||||
|
||||
# ── Poll /healthz ──────────────────────────────────────────────────────
|
||||
step "wait for /healthz"
|
||||
DEADLINE=$(( $(date +%s) + 30 ))
|
||||
HEALTHZ_BODY=""
|
||||
while [ "$(date +%s)" -lt "$DEADLINE" ]; do
|
||||
if ! kill -0 "$PID" 2>/dev/null; then
|
||||
sed 's/^/ /' "$LOG" >&2 || true
|
||||
fail "host died during boot"
|
||||
fi
|
||||
if HEALTHZ_BODY="$(curl -fsS -m 1 "http://127.0.0.1:$PORT/healthz" 2>/dev/null)"; then
|
||||
break
|
||||
fi
|
||||
sleep 0.2
|
||||
done
|
||||
[ -n "$HEALTHZ_BODY" ] || { sed 's/^/ /' "$LOG" >&2 || true; fail "/healthz never responded within 30s"; }
|
||||
|
||||
# ── Verify bundle deep-load ────────────────────────────────────────────
|
||||
step "/healthz body: $HEALTHZ_BODY"
|
||||
echo "$HEALTHZ_BODY" | grep -q '"status":"ok"' \
|
||||
|| fail "/healthz didn't return status:ok"
|
||||
echo "$HEALTHZ_BODY" | grep -q '"bundle":"PhpQml\\\\Bridge\\\\Publisher"' \
|
||||
|| fail "/healthz missing bundle field — HealthController deep-load broken"
|
||||
|
||||
# ── Verify the cache/log redirect actually fired ───────────────────────
|
||||
step "verify Symfony wrote cache to user data dir, not the read-only staging"
|
||||
# Qt's QStandardPaths::AppDataLocation on Linux is $XDG_DATA_HOME/<org>/<app>,
|
||||
# org="php-qml" comes from main.cpp setOrganizationName, app="todo" from setApplicationName.
|
||||
USER_DATA="$DATA_DIR/share/php-qml/$APP_NAME"
|
||||
[ -d "$USER_DATA/var/cache" ] \
|
||||
|| fail "user-data var/cache missing at $USER_DATA — APP_CACHE_DIR override didn't fire"
|
||||
# And not into the staged tree (which is chmod -w anyway):
|
||||
if [ -d "$ROOT/usr/share/$APP_NAME/symfony/var/cache/prod" ] && \
|
||||
[ "$(ls -A "$ROOT/usr/share/$APP_NAME/symfony/var/cache/prod" 2>/dev/null)" ]; then
|
||||
fail "Symfony wrote into the read-only staging tree — Kernel::getCacheDir override broken"
|
||||
fi
|
||||
|
||||
step "All bundled-supervisor assertions passed."
|
||||
@@ -4,18 +4,33 @@ declare(strict_types=1);
|
||||
|
||||
namespace PhpQml\Bridge\Controller;
|
||||
|
||||
use PhpQml\Bridge\Publisher;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
|
||||
/**
|
||||
* Readiness probe used by the Qt host to detect when the backend is up.
|
||||
* See PLAN.md §3 (*Startup*, step 4).
|
||||
*
|
||||
* Publisher is injected purely as a deep-health canary: if the bridge
|
||||
* bundle's autoload or container wiring is broken (e.g. a packaging build
|
||||
* with a dangling vendor path-repo symlink), this controller can't even
|
||||
* be constructed, so /healthz fails 500 instead of misleadingly returning
|
||||
* 200 against a half-loaded bundle.
|
||||
*/
|
||||
final class HealthController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly Publisher $publisher,
|
||||
) {
|
||||
}
|
||||
|
||||
#[Route('/healthz', name: 'php_qml_bridge_healthz', methods: ['GET'])]
|
||||
public function __invoke(): JsonResponse
|
||||
{
|
||||
return new JsonResponse(['status' => 'ok']);
|
||||
return new JsonResponse([
|
||||
'status' => 'ok',
|
||||
'bundle' => $this->publisher::class,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@
|
||||
# before launching FrankenPHP.
|
||||
#
|
||||
# Caddyfile {$VAR:default} syntax substitutes env vars at parse time.
|
||||
|
||||
{
|
||||
auto_https off
|
||||
admin off
|
||||
|
||||
@@ -6,6 +6,11 @@ SYMFONY_DIR := symfony
|
||||
QML_SRC_DIR := qml
|
||||
BUILD_DIR := build/qml
|
||||
QT_BIN := $(BUILD_DIR)/skeleton
|
||||
# Path to framework/php (path-repo source) and packaging/linux (build-appimage.sh).
|
||||
# Both are framework-tree relative; bin/php-qml-init rewrites them at scaffold time
|
||||
# to either an absolute framework path (default) or a vendored copy under .bridge/.
|
||||
BUNDLE_SRC := ../../php
|
||||
PACKAGING := ../../packaging/linux
|
||||
|
||||
.PHONY: help
|
||||
help: ## Show available targets
|
||||
@@ -34,7 +39,43 @@ doctor-connect: ## Run bridge:doctor with backend connectivity probe
|
||||
|
||||
.PHONY: clean
|
||||
clean: ## Remove build artefacts
|
||||
rm -rf $(BUILD_DIR)
|
||||
rm -rf $(BUILD_DIR) build/staging-symfony
|
||||
|
||||
.PHONY: staging-symfony
|
||||
staging-symfony: ## Stage a --no-dev composer copy of symfony for AppImage assembly
|
||||
# See examples/todo/Makefile for the rationale; same logic, mirrored here
|
||||
# so scaffolded apps inherit a working AppImage flow without copy-paste.
|
||||
# BUNDLE_SRC may be absolute (after `php-qml-init` rewrites it for a
|
||||
# scaffolded app) or relative-to-symfony (framework default `../../php`);
|
||||
# the case-statement handles both.
|
||||
rm -rf build/staging-symfony
|
||||
rsync -a --delete \
|
||||
--exclude='vendor/' \
|
||||
--exclude='var/cache/' --exclude='var/log/' \
|
||||
$(SYMFONY_DIR)/ build/staging-symfony/
|
||||
set -e; case "$(BUNDLE_SRC)" in \
|
||||
/*) BUNDLE_ABS="$(BUNDLE_SRC)" ;; \
|
||||
*) BUNDLE_ABS="$$(cd $(SYMFONY_DIR)/$(BUNDLE_SRC) && pwd)" ;; \
|
||||
esac; \
|
||||
sed -i "s|\"$(BUNDLE_SRC)\"|\"$$BUNDLE_ABS\"|" build/staging-symfony/composer.json
|
||||
sed -i 's|"symlink": true|"symlink": false|' build/staging-symfony/composer.json
|
||||
rm -f build/staging-symfony/composer.lock
|
||||
cd build/staging-symfony && composer install --no-dev --no-interaction --classmap-authoritative
|
||||
|
||||
.PHONY: appimage
|
||||
appimage: build staging-symfony ## Package as a single-file Linux AppImage at build/Skeleton-x86_64.AppImage
|
||||
$(PACKAGING)/build-appimage.sh \
|
||||
--app-name skeleton \
|
||||
--host-binary $(QT_BIN) \
|
||||
--symfony-dir build/staging-symfony \
|
||||
--frankenphp $${FRANKENPHP:-frankenphp} \
|
||||
--caddyfile Caddyfile \
|
||||
--desktop packaging/skeleton.desktop \
|
||||
--icon packaging/skeleton.png \
|
||||
--output build/Skeleton-x86_64.AppImage \
|
||||
$${APPIMAGE_UPDATE_INFO:+--update-info "$$APPIMAGE_UPDATE_INFO"}
|
||||
@echo
|
||||
@echo "AppImage built. Test with: ./build/Skeleton-x86_64.AppImage"
|
||||
|
||||
.PHONY: quality
|
||||
quality: build ## Run PHPStan, php-cs-fixer (check), PHPUnit, qmllint, maker snapshots
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=php-qml Skeleton
|
||||
Comment=php-qml application scaffold (replace with your own)
|
||||
Exec=skeleton
|
||||
Icon=skeleton
|
||||
Categories=Utility;
|
||||
Terminal=false
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 209 B |
Reference in new issue
Block a user