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
88 changes: 0 additions & 88 deletions .github/workflows/release-cef-host.yml

This file was deleted.

20 changes: 20 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# flutter_cef — developer convenience targets.
#
# publish-cef-host: build the SANDBOXED (CEF_HOST_ADHOC=OFF), Developer-ID cef_host
# and publish it to GCS keyed by a content hash of the build inputs. Run this when
# native/cef_host/ or the CEF version changes so consumers can fetch a matching host
# (fetch_cef_host.sh) instead of building from source. Idempotent — re-running with
# an unchanged host is a no-op. Needs a Developer ID Application identity in your
# keychain and gsutil write on gs://flutterflow-downloads (auto-resolves the
# identity; override CODESIGN_ID / GCS_PREFIX to customize).
#
# make publish-cef-host
# GCS_PREFIX=campus_prebuilt_cef_host-staging make publish-cef-host # dry-run to staging

CODESIGN_ID ?= $(shell security find-identity -v -p codesigning | grep 'Developer ID Application' | head -1 | awk '{print $$2}')

.PHONY: publish-cef-host
publish-cef-host:
@test -n "$(CODESIGN_ID)" || { echo "error: no 'Developer ID Application' identity in the keychain"; exit 1; }
@command -v gsutil >/dev/null 2>&1 || { echo "error: gsutil not found (install the Google Cloud SDK)"; exit 1; }
CODESIGN_ID="$(CODESIGN_ID)" bash packages/flutter_cef_macos/tool/publish-cef-host.sh
12 changes: 0 additions & 12 deletions packages/flutter_cef_macos/cef_host_prebuilt.json

This file was deleted.

42 changes: 42 additions & 0 deletions packages/flutter_cef_macos/tool/cef_host_hash.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#!/usr/bin/env bash
# Deterministic content hash of the cef_host build inputs.
#
# Sourced by BOTH fetch_cef_host.sh (consumer, at pod install) and
# publish-cef-host.sh (CI) so they ALWAYS compute the same digest from the same
# source tree — that digest is the GCS object key, so any drift here is a silent
# cache miss. Prints a 64-hex digest to stdout.
#
# Inputs = native/build_cef_host.sh (carries CEF_VERSION + the CEF dist sha pin +
# the signing/adhoc defaults) + every source file under native/cef_host/
# (excluding the prebuilt/ and build/ OUTPUT dirs). The huge CEF binary dist is
# NOT hashed — it is pinned transitively by build_cef_host.sh's CEF_VERSION.
#
# Usage: . cef_host_hash.sh ; cef_host_input_hash <native_dir>
# <native_dir> = .../packages/flutter_cef_macos/native
set -euo pipefail

# sha256 of the given file(s), or of stdin when no args. Portable across bash/zsh
# (no reliance on word-splitting a command string) and macOS/Linux.
_cefhost_sha256() {
if command -v shasum >/dev/null 2>&1; then shasum -a 256 "$@"; else sha256sum "$@"; fi
}

cef_host_input_hash() {
local native_dir="$1"
# Sorted, path-relative list of build-input files. LC_ALL=C makes the sort
# byte-stable across machines. We emit "<relpath>\n<filesha>\n" per file so
# BOTH content changes and path/renames move the final digest.
(
cd "$native_dir"
export LC_ALL=C
{
printf '%s\n' "build_cef_host.sh"
find cef_host -type f \
-not -path 'cef_host/prebuilt/*' \
-not -path 'cef_host/build/*'
} | LC_ALL=C sort -u | while IFS= read -r f; do
printf '%s\n' "$f"
_cefhost_sha256 "$f" | awk '{print $1}'
done
) | _cefhost_sha256 | awk '{print $1}'
}
115 changes: 64 additions & 51 deletions packages/flutter_cef_macos/tool/fetch_cef_host.sh
Original file line number Diff line number Diff line change
@@ -1,78 +1,91 @@
#!/usr/bin/env bash
# Fetch the prebuilt, version-matched cef_host.app — run at `pod install` via the podspec's
# prepare_command. Downloads + SHA256-verifies the artifact named in cef_host_prebuilt.json,
# caches it, and extracts cef_host.app into native/cef_host/prebuilt/ where the :after_compile
# script phase embeds it. Self-locating (CWD-independent). Fail-OPEN: any problem leaves no
# prebuilt, and the build falls back to FLUTTER_CEF_HOST / build-from-source.
# Fetch a prebuilt, Developer-ID-signed cef_host.app keyed by a CONTENT HASH of
# the build inputs, from public GCS. Runs at `pod install` via the podspec's
# prepare_command; the :after_compile phase then embeds native/cef_host/prebuilt/
# cef_host.app into the app's Contents/Frameworks. Self-locating (CWD-independent).
#
# Escape hatch: FLUTTER_CEF_FROM_SOURCE=1 skips the fetch entirely (co-dev builds cef_host from
# source via native/build_cef_host.sh and points the app at it with $FLUTTER_CEF_HOST).
set -uo pipefail
# The hash (see cef_host_hash.sh) is derived from the checked-out native/cef_host
# sources + build_cef_host.sh, so it is identical to what the publisher computed —
# no committed manifest, no per-commit bookkeeping, release-model-agnostic (any
# SHA/branch/tag pin that checks out the same native sources resolves to the same
# object). Fail-OPEN on network/missing (co-dev + offline builds fall back to
# build-from-source / FLUTTER_CEF_HOST); fail-CLOSED on checksum mismatch.
set -euo pipefail

# Escape hatch: co-dev / build-from-source (native/build_cef_host.sh + a make host).
if [ -n "${FLUTTER_CEF_FROM_SOURCE:-}" ]; then
echo "[flutter_cef] FLUTTER_CEF_FROM_SOURCE set — skipping prebuilt fetch (build-from-source)"
echo "[flutter_cef] FLUTTER_CEF_FROM_SOURCE set — skipping prebuilt fetch (build from source)."
exit 0
fi

HERE="$(cd "$(dirname "$0")" && pwd)" # .../flutter_cef_macos/tool
PKG="$(cd "$HERE/.." && pwd)" # .../flutter_cef_macos
MANIFEST="$PKG/cef_host_prebuilt.json"
DEST="$PKG/native/cef_host/prebuilt"

[ -f "$MANIFEST" ] || { echo "[flutter_cef] no $MANIFEST — skipping fetch"; exit 0; }
HERE="$(cd "$(dirname "$0")" && pwd)" # .../tool
PKG="$(cd "$HERE/.." && pwd)" # .../flutter_cef_macos
NATIVE="$PKG/native"
DEST="$NATIVE/cef_host/prebuilt"

# Only macos-arm64 is published today; x86_64 builds from source.
case "$(uname -m)" in
arm64) arch=arm64 ;;
x86_64) arch=x86_64 ;;
*) echo "[flutter_cef] unsupported arch $(uname -m) — skipping fetch"; exit 0 ;;
arm64) arch=arm64 ;;
*) echo "[flutter_cef] arch $(uname -m) has no prebuilt cef_host — build from source."; exit 0 ;;
esac
key="macos-${arch}-dev"

# Parse the manifest with python3 (present on every macOS dev box). Prints: base file sha src ver
read -r base file sha src ver <<EOF
$(python3 - "$MANIFEST" "$key" <<'PY'
import json, sys
m = json.load(open(sys.argv[1])); art = m.get("artifacts", {}).get(sys.argv[2])
if not art:
print("NONE NONE NONE NONE NONE")
else:
print(m["base_url"], art["file"], art["sha256"], m.get("source_sha", ""), m.get("cef_version", ""))
PY
)
EOF
# shellcheck source=cef_host_hash.sh
. "$HERE/cef_host_hash.sh"
HASH="$(cef_host_input_hash "$NATIVE")"

if [ "$base" = "NONE" ] || [ -z "$base" ]; then
echo "[flutter_cef] no prebuilt for $key in manifest — skipping (build from source)"
exit 0
fi
BASE="${FLUTTER_CEF_GCS_BASE:-https://storage.googleapis.com/flutterflow-downloads/campus_prebuilt_cef_host}"
FILE="cef_host-macos-${arch}.tar.gz"
URL="$BASE/$HASH/$FILE"
SHA_URL="$URL.sha256"

# Already current? (the tarball carries cef_host_source_sha.txt next to cef_host.app)
if [ -d "$DEST/cef_host.app" ] && [ -f "$DEST/cef_host_source_sha.txt" ] \
&& [ "$(cat "$DEST/cef_host_source_sha.txt" 2>/dev/null)" = "$src" ]; then
echo "[flutter_cef] prebuilt cef_host already current ($src) — skipping fetch"
# Already current? the extracted prebuilt carries the input hash it was built from.
STAMP="$DEST/cef_host_input_hash.txt"
if [ -d "$DEST/cef_host.app" ] && [ -f "$STAMP" ] && [ "$(cat "$STAMP")" = "$HASH" ]; then
echo "[flutter_cef] prebuilt cef_host.app already current ($HASH) — skipping fetch."
exit 0
fi

CACHE="${FLUTTER_CEF_CACHE:-$HOME/.cache/flutter_cef}/prebuilt/$src/$arch"
CACHE="${FLUTTER_CEF_CACHE:-$HOME/.cache/flutter_cef}/prebuilt/$HASH/$arch"
mkdir -p "$CACHE"
tarball="$CACHE/$file"
tarball="$CACHE/$FILE"

sha256_file() {
if command -v shasum >/dev/null 2>&1; then shasum -a 256 "$1" | awk '{print $1}'
else sha256sum "$1" | awk '{print $1}'; fi
}

# The expected tarball sha256 (transport integrity) lives beside the object.
# Fail-OPEN if unreachable: no published host for this hash yet (a fresh native
# change before CI publishes, or offline) -> build from source.
expected=""
if ! expected="$(curl -fsSL --retry 3 --retry-delay 1 "$SHA_URL" 2>/dev/null | awk '{print $1}')"; then
echo "[flutter_cef] no published cef_host for hash $HASH ($SHA_URL unreachable)."
echo "[flutter_cef] building from source (dev), or CI will publish it shortly."
exit 0
fi

if [ ! -f "$tarball" ] || [ "$(shasum -a 256 "$tarball" 2>/dev/null | awk '{print $1}')" != "$sha" ]; then
echo "[flutter_cef] downloading prebuilt cef_host ($key, cef $ver)…"
if ! curl -fL --retry 3 "$base/$file" -o "$tarball.part"; then
echo "[flutter_cef] download failed — leaving no prebuilt (FLUTTER_CEF_HOST / from-source will be used)" >&2
rm -f "$tarball.part"; exit 0
# (Re)download on cache miss or a stale/corrupt cached tarball.
if [ ! -f "$tarball" ] || [ "$(sha256_file "$tarball")" != "$expected" ]; then
echo "[flutter_cef] downloading prebuilt cef_host: $URL"
if ! curl -fL --retry 3 --retry-delay 1 -o "$tarball.part" "$URL"; then
echo "[flutter_cef] download failed — building from source." >&2
rm -f "$tarball.part"
exit 0
fi
got="$(shasum -a 256 "$tarball.part" | awk '{print $1}')"
if [ "$got" != "$sha" ]; then
echo "[flutter_cef] SHA256 mismatch (got $got, want $sha) — refusing the artifact" >&2
rm -f "$tarball.part"; exit 1
actual="$(sha256_file "$tarball.part")"
if [ "$actual" != "$expected" ]; then
echo "[flutter_cef] SHA256 mismatch for $FILE (expected $expected, got $actual) — refusing." >&2
rm -f "$tarball.part"
exit 1 # fail-CLOSED: never embed an unverified host
fi
mv "$tarball.part" "$tarball"
fi

echo "[flutter_cef] extracting prebuilt cef_host -> $DEST"
mkdir -p "$DEST"
rm -rf "$DEST/cef_host.app"
# tar preserves the inside-out Developer-ID signature; the .app + provenance
# stamps (source_sha / version / input_hash) land in prebuilt/.
tar -xzf "$tarball" -C "$DEST"
echo "[flutter_cef] prebuilt cef_host ready ($src)"
printf '%s\n' "$HASH" > "$STAMP" # stamp even if the tarball predates the field
echo "[flutter_cef] prebuilt cef_host ready ($HASH)."
85 changes: 85 additions & 0 deletions packages/flutter_cef_macos/tool/publish-cef-host.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#!/usr/bin/env bash
# Build the SANDBOXED (CEF_HOST_ADHOC=OFF, Developer-ID) cef_host, key it by a
# content hash of the build inputs, and idempotently publish it to public GCS.
# Run by the private flutter_cef Codemagic workflow (which holds the signing
# material + a GCS-writable service account) on push-to-main and cef-host-v* tags.
#
# The SANDBOXED variant is deliberate: the ad-hoc variant (get-task-allow + mock
# keychain + Mach-port bypass) fails to render agent_ui in a consuming app. The
# Developer-ID signature is inside-out; release consumers re-sign it with their
# own identity, so only the (rare) direct-run case depends on it.
#
# Requires: gsutil/gcloud authed with object-create on gs://$GCS_BUCKET, and a
# Developer-ID Application identity in the keychain named by $CODESIGN_ID.
set -euo pipefail

HERE="$(cd "$(dirname "$0")" && pwd)" # .../tool
PKG="$(cd "$HERE/.." && pwd)" # .../flutter_cef_macos
NATIVE="$PKG/native"
REPO="$(cd "$PKG/../.." && pwd)" # repo root (git provenance)

: "${CODESIGN_ID:?CODESIGN_ID (Developer ID Application identity) must be set}"
GCS_BUCKET="${GCS_BUCKET:-flutterflow-downloads}"
GCS_PREFIX="${GCS_PREFIX:-campus_prebuilt_cef_host}"
arch=arm64
FILE="cef_host-macos-${arch}.tar.gz"

# shellcheck source=cef_host_hash.sh
. "$HERE/cef_host_hash.sh"
HASH="$(cef_host_input_hash "$NATIVE")"
echo "[publish] cef_host input hash: $HASH"

DST="gs://$GCS_BUCKET/$GCS_PREFIX/$HASH/$FILE"

# Idempotency: this exact tree was already built + uploaded -> nothing to do.
if gsutil -q stat "$DST" 2>/dev/null; then
echo "[publish] $DST already exists — nothing to do."
exit 0
fi

# --- Build the sandboxed, Developer-ID-signed variant ---
OUT="$(mktemp -d)/out"
mkdir -p "$OUT"
CEF_HOST_ADHOC=OFF CODESIGN_ID="$CODESIGN_ID" bash "$NATIVE/build_cef_host.sh" "$OUT"
APP="$OUT/cef_host.app"
[ -d "$APP" ] || { echo "::error:: cef_host.app not produced by build" >&2; exit 1; }

# Fail-fast: it must be Developer-ID signed and NOT ad-hoc. Capture the output
# (|| true) and string-match rather than piping into grep under `set -o pipefail`
# — codesign -dvv can exit non-zero on a perfectly valid signature, which would
# false-fail a `codesign … | grep` pipeline.
sig="$(codesign -dvv "$APP" 2>&1 || true)"
case "$sig" in
*"Developer ID Application"*) : ;;
*) echo "::error:: cef_host.app is not Developer-ID signed (ad-hoc?):" >&2
printf '%s\n' "$sig" | head -3 >&2
exit 1 ;;
esac

# --- Provenance stamps beside the app (informational; the URL is the hash) ---
SRC_SHA="$(git -C "$REPO" rev-parse HEAD)"
CEF_VER="$(grep '^CEF_VERSION=' "$NATIVE/build_cef_host.sh" | head -1 | cut -d'"' -f2)"
printf '%s\n' "$SRC_SHA" > "$OUT/cef_host_source_sha.txt"
printf '%s\n' "$CEF_VER" > "$OUT/cef_version.txt"
printf '%s\n' "$HASH" > "$OUT/cef_host_input_hash.txt"

# --- Tar + sha256 (COPYFILE_DISABLE keeps ._* AppleDouble junk out of the tar) ---
STAGE="$(mktemp -d)"
TARBALL="$STAGE/$FILE"
COPYFILE_DISABLE=1 tar -czf "$TARBALL" -C "$OUT" \
cef_host.app cef_host_source_sha.txt cef_version.txt cef_host_input_hash.txt
if command -v shasum >/dev/null 2>&1; then
TAR_SHA="$(shasum -a 256 "$TARBALL" | awk '{print $1}')"
else
TAR_SHA="$(sha256sum "$TARBALL" | awk '{print $1}')"
fi
printf '%s %s\n' "$TAR_SHA" "$FILE" > "$TARBALL.sha256"

# --- Upload (re-check to close a publish race; objects are immutable) ---
if gsutil -q stat "$DST" 2>/dev/null; then
echo "[publish] $DST appeared during build — skipping upload."
exit 0
fi
gsutil -h "Cache-Control:public,max-age=31536000,immutable" cp "$TARBALL" "$DST"
gsutil -h "Cache-Control:public,max-age=31536000,immutable" cp "$TARBALL.sha256" "$DST.sha256"
echo "[publish] uploaded $DST (tarball sha256 $TAR_SHA)"
Loading
Loading