fix(supply-chain): verify CI and production inputs
Pin external actions, images, toolchains, native archives, and tvOS engine artifacts; enforce fail-closed CI checks and keep website privacy disclosures aligned with shipped behavior.
This commit is contained in:
@@ -3,8 +3,8 @@
|
||||
# extract it into a shared cache, and write tvos/Flutter/Generated.xcconfig
|
||||
# so Xcode picks it up via FLUTTER_LOCAL_ENGINE.
|
||||
#
|
||||
# Reads the engine version from tvos/engine.version. Re-runs are cheap —
|
||||
# skips download if the cache already has the matching version.
|
||||
# Reads the engine version and reviewed SHA-256 from tvos/engine.version and
|
||||
# tvos/engine.sha256. Re-runs are cheap —
|
||||
#
|
||||
# Usage:
|
||||
# tvos/scripts/fetch_engine.sh
|
||||
@@ -19,36 +19,54 @@ TVOS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
REPO_ROOT="$(cd "${TVOS_DIR}/.." && pwd)"
|
||||
|
||||
VERSION_FILE="${TVOS_DIR}/engine.version"
|
||||
if [[ ! -f "$VERSION_FILE" ]]; then
|
||||
echo "error: $VERSION_FILE missing" >&2
|
||||
SHA256_FILE="${TVOS_DIR}/engine.sha256"
|
||||
if [[ ! -f "$VERSION_FILE" || ! -f "$SHA256_FILE" ]]; then
|
||||
echo "error: tvOS engine version/checksum metadata is missing" >&2
|
||||
exit 1
|
||||
fi
|
||||
VERSION="$(tr -d '[:space:]' < "$VERSION_FILE")"
|
||||
EXPECTED_SHA256="$(tr -d '[:space:]' < "$SHA256_FILE")"
|
||||
if [[ -z "$VERSION" ]]; then
|
||||
echo "error: tvos/engine.version is empty" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! "$EXPECTED_SHA256" =~ ^[0-9a-f]{64}$ ]]; then
|
||||
echo "error: tvos/engine.sha256 must contain one lowercase SHA-256 digest" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CACHE_ROOT="${FLUTTER_TVOS_ENGINE_CACHE:-$HOME/.cache/flutter-tvos-engine}"
|
||||
RELEASES_URL="${FLUTTER_TVOS_RELEASES_URL:-https://github.com/edde746/flutter-tvos}"
|
||||
RELEASES_URL="${FLUTTER_TVOS_RELEASES_URL:-https://github.com/edde746/flutter-plezy}"
|
||||
|
||||
ENGINE_DIR="${CACHE_ROOT}/v${VERSION}"
|
||||
TARBALL_URL="${RELEASES_URL}/releases/download/v${VERSION}/flutter-tvos-${VERSION}.tar.gz"
|
||||
STAMP="${ENGINE_DIR}/.installed-${VERSION}"
|
||||
STAMP="${ENGINE_DIR}/.installed"
|
||||
INSTALL_ID="${VERSION} ${EXPECTED_SHA256}"
|
||||
|
||||
if [[ ! -f "$STAMP" ]]; then
|
||||
if [[ ! -f "$STAMP" || "$(<"$STAMP")" != "$INSTALL_ID" ]]; then
|
||||
echo "[fetch_engine] downloading ${TARBALL_URL}"
|
||||
mkdir -p "$ENGINE_DIR"
|
||||
mkdir -p "$CACHE_ROOT"
|
||||
TMP_TAR="$(mktemp -t flutter-tvos-engine.XXXXXX.tar.gz)"
|
||||
trap 'rm -f "$TMP_TAR"' EXIT
|
||||
TMP_ENGINE="$(mktemp -d "${CACHE_ROOT}/.engine.XXXXXX")"
|
||||
cleanup() {
|
||||
rm -f "${TMP_TAR:-}"
|
||||
if [[ -n "${TMP_ENGINE:-}" ]]; then
|
||||
rm -rf "$TMP_ENGINE"
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
curl -fL --progress-bar -o "$TMP_TAR" "$TARBALL_URL"
|
||||
echo "[fetch_engine] extracting to $ENGINE_DIR"
|
||||
tar -xzf "$TMP_TAR" -C "$ENGINE_DIR"
|
||||
touch "$STAMP"
|
||||
printf '%s %s\n' "$EXPECTED_SHA256" "$TMP_TAR" | shasum -a 256 -c -
|
||||
echo "[fetch_engine] extracting verified archive to $ENGINE_DIR"
|
||||
tar -xzf "$TMP_TAR" -C "$TMP_ENGINE"
|
||||
printf '%s\n' "$INSTALL_ID" > "$TMP_ENGINE/.installed"
|
||||
rm -rf "$ENGINE_DIR"
|
||||
mv "$TMP_ENGINE" "$ENGINE_DIR"
|
||||
TMP_ENGINE=""
|
||||
rm -f "$TMP_TAR"
|
||||
trap - EXIT
|
||||
else
|
||||
echo "[fetch_engine] using cached engine at $ENGINE_DIR"
|
||||
echo "[fetch_engine] using verified cached engine at $ENGINE_DIR"
|
||||
fi
|
||||
|
||||
# Locate a host Flutter SDK for flutter CLI invocation during the build.
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
require 'xcodeproj'
|
||||
|
||||
PROJECT_PATH = File.expand_path('../Runner.xcodeproj', __dir__)
|
||||
SCHEME_PATH = File.expand_path('../Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme', __dir__)
|
||||
project = Xcodeproj::Project.open(PROJECT_PATH)
|
||||
runner = project.targets.find { |t| t.name == 'Runner' }
|
||||
raise 'Runner target not found' unless runner
|
||||
@@ -66,6 +67,53 @@ system_shelf_ref = ensure_file(runner_group, 'SystemShelfPlugin.swift')
|
||||
ensure_source(runner, system_shelf_ref)
|
||||
ensure_file(runner_group, 'Runner.entitlements')
|
||||
|
||||
tests_group = main_group['RunnerTests'] || main_group.new_group('RunnerTests', 'RunnerTests')
|
||||
test_target = project.targets.find { |target| target.name == 'RunnerTests' }
|
||||
unless test_target
|
||||
test_target = project.new_target(:unit_test_bundle, 'RunnerTests', :tvos, '14.0')
|
||||
end
|
||||
test_target.product_type = 'com.apple.product-type.bundle.unit-test'
|
||||
test_target.frameworks_build_phase.files.delete_if do |build_file|
|
||||
build_file.file_ref&.display_name == 'Foundation.framework'
|
||||
end
|
||||
project.files.select { |file| file.display_name == 'Foundation.framework' }.each do |file_ref|
|
||||
still_used = project.targets.any? do |target|
|
||||
target.frameworks_build_phase.files_references.include?(file_ref)
|
||||
end
|
||||
file_ref.remove_from_project unless still_used
|
||||
end
|
||||
runner_test_sources = %w[
|
||||
TvosEventDeliveryCoordinatorTests.swift
|
||||
]
|
||||
test_target.source_build_phase.files.delete_if do |build_file|
|
||||
file_ref = build_file.file_ref
|
||||
file_ref && !runner_test_sources.include?(file_ref.display_name)
|
||||
end
|
||||
tests_group.files.reject { |file_ref| runner_test_sources.include?(file_ref.display_name) }.each do |file_ref|
|
||||
file_ref.remove_from_project
|
||||
end
|
||||
runner_test_sources.each do |filename|
|
||||
ensure_source(test_target, ensure_file(tests_group, filename))
|
||||
end
|
||||
test_target.add_dependency(runner) unless test_target.dependencies.any? { |dependency| dependency.target == runner }
|
||||
|
||||
test_target.build_configurations.each do |config|
|
||||
settings = config.build_settings
|
||||
settings['BUNDLE_LOADER'] = '$(TEST_HOST)'
|
||||
settings.delete('CODE_SIGNING_ALLOWED')
|
||||
settings['GENERATE_INFOPLIST_FILE'] = 'YES'
|
||||
settings['PRODUCT_BUNDLE_IDENTIFIER'] = 'com.edde746.plezy.RunnerTests'
|
||||
settings['SDKROOT'] = 'appletvos'
|
||||
settings['SUPPORTED_PLATFORMS'] = 'appletvos appletvsimulator'
|
||||
settings['SWIFT_VERSION'] = '5.0'
|
||||
settings['TARGETED_DEVICE_FAMILY'] = '3'
|
||||
settings['TEST_HOST'] = '$(BUILT_PRODUCTS_DIR)/Runner.app/Runner'
|
||||
settings['TVOS_DEPLOYMENT_TARGET'] = '14.0'
|
||||
end
|
||||
|
||||
event_delivery_ref = ensure_file(runner_group, 'TvosEventDeliveryCoordinator.swift')
|
||||
ensure_source(runner, event_delivery_ref)
|
||||
|
||||
extension_group = main_group['TopShelfExtension'] || main_group.new_group('TopShelfExtension', 'TopShelfExtension')
|
||||
top_shelf_ref = ensure_file(extension_group, 'TopShelfProvider.swift')
|
||||
ensure_file(extension_group, 'Info.plist')
|
||||
@@ -158,5 +206,13 @@ ensure_shell_script(
|
||||
'/bin/bash "$SOURCE_ROOT/scripts/xcode_appletv.sh" sync_version' + "\n"
|
||||
)
|
||||
|
||||
scheme = Xcodeproj::XCScheme.new(SCHEME_PATH)
|
||||
unless scheme.test_action.testables.any? do |testable|
|
||||
testable.buildable_references.any? { |reference| reference.target_name == test_target.name }
|
||||
end
|
||||
scheme.add_test_target(test_target)
|
||||
end
|
||||
scheme.save!
|
||||
|
||||
project.save
|
||||
puts 'Saved Top Shelf wiring'
|
||||
|
||||
@@ -307,6 +307,55 @@ ResolveEngineOutput() {
|
||||
return 1
|
||||
}
|
||||
|
||||
ResolveDartAotRuntime() {
|
||||
local host_tools="$1"
|
||||
local packaged_runtime="$host_tools/dart-sdk/bin/dartaotruntime"
|
||||
if [[ -x "$packaged_runtime" ]] && "$packaged_runtime" --version >/dev/null 2>&1; then
|
||||
printf '%s\n' "$packaged_runtime"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local flutter_runtime="${FLUTTER_ROOT:-}/bin/cache/dart-sdk/bin/dartaotruntime"
|
||||
local packaged_version_file="$host_tools/dart-sdk/version"
|
||||
local flutter_version_file="${FLUTTER_ROOT:-}/bin/cache/dart-sdk/version"
|
||||
if [[ ! -x "$flutter_runtime" || ! -f "$packaged_version_file" || ! -f "$flutter_version_file" ]]; then
|
||||
echo " └─ERROR: the packaged Dart runtime cannot execute on this host and no compatible Flutter runtime was found" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
local packaged_version
|
||||
local flutter_version
|
||||
packaged_version="$(tr -d '[:space:]' < "$packaged_version_file")"
|
||||
flutter_version="$(tr -d '[:space:]' < "$flutter_version_file")"
|
||||
if [[ "$packaged_version" != "$flutter_version" ]]; then
|
||||
echo " └─ERROR: the packaged Dart runtime cannot execute on this host and Flutter's Dart version does not match ($flutter_version != $packaged_version)" >&2
|
||||
return 1
|
||||
fi
|
||||
if ! "$flutter_runtime" --version >/dev/null 2>&1; then
|
||||
echo " └─ERROR: Flutter's Dart runtime cannot execute on this host" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
printf '%s\n' "$flutter_runtime"
|
||||
}
|
||||
|
||||
ResolveFrontendServer() {
|
||||
local host_tools="$1"
|
||||
local dart_aot_runtime="$2"
|
||||
local frontend_server="$host_tools/dart-sdk/bin/snapshots/frontend_server_aot.dart.snapshot"
|
||||
if [[ "$dart_aot_runtime" != "$host_tools/dart-sdk/bin/dartaotruntime" ]]; then
|
||||
frontend_server="$FLUTTER_ROOT/bin/cache/dart-sdk/bin/snapshots/frontend_server_aot.dart.snapshot"
|
||||
elif [[ ! -f "$frontend_server" ]]; then
|
||||
frontend_server="$host_tools/gen/frontend_server_aot.dart.snapshot"
|
||||
fi
|
||||
|
||||
if [[ ! -f "$frontend_server" ]]; then
|
||||
echo " └─ERROR: compatible frontend_server snapshot was not found" >&2
|
||||
return 1
|
||||
fi
|
||||
printf '%s\n' "$frontend_server"
|
||||
}
|
||||
|
||||
BuildAppDebug() {
|
||||
# Host tools (frontend_server, patched SDK, dartaotruntime) ship in
|
||||
# host_release for both debug and release consumers — the frontend_server
|
||||
@@ -353,6 +402,8 @@ BuildAppDebug() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
DART_AOT_RUNTIME=$(ResolveDartAotRuntime "$HOST_TOOLS") || return 1
|
||||
|
||||
# flutter build bundle produces: AssetManifest, FontManifest, NOTICES,
|
||||
# shaders, fonts, assets, packages, plus a kernel_blob.bin and
|
||||
# isolate_snapshot_data compiled against the stock flutter engine. We
|
||||
@@ -369,12 +420,9 @@ BuildAppDebug() {
|
||||
return 1
|
||||
}
|
||||
|
||||
echo " └─Compiling tvOS kernel via local engine frontend_server"
|
||||
FRONTEND_SERVER="$HOST_TOOLS/dart-sdk/bin/snapshots/frontend_server_aot.dart.snapshot"
|
||||
if [ ! -f "$FRONTEND_SERVER" ]; then
|
||||
FRONTEND_SERVER="$HOST_TOOLS/gen/frontend_server_aot.dart.snapshot"
|
||||
fi
|
||||
"$HOST_TOOLS/dart-sdk/bin/dartaotruntime" \
|
||||
echo " └─Compiling tvOS kernel via compatible frontend_server"
|
||||
FRONTEND_SERVER=$(ResolveFrontendServer "$HOST_TOOLS" "$DART_AOT_RUNTIME") || return 1
|
||||
"$DART_AOT_RUNTIME" \
|
||||
"$FRONTEND_SERVER" \
|
||||
--sdk-root "$HOST_TOOLS/flutter_patched_sdk" \
|
||||
--tfa --target=flutter \
|
||||
@@ -515,6 +563,8 @@ BuildAppRelease() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
DART_AOT_RUNTIME=$(ResolveDartAotRuntime "$HOST_TOOLS") || return 1
|
||||
|
||||
echo " └─Generate flutter_assets via flutter build bundle (release)"
|
||||
mkdir -p "$OUTDIR/App.framework/flutter_assets"
|
||||
(
|
||||
@@ -535,11 +585,8 @@ BuildAppRelease() {
|
||||
echo " └─Compiling AOT kernel via local engine frontend_server"
|
||||
# The snapshot under dart-sdk/bin/snapshots/ is the actual AOT-compiled one;
|
||||
# the one under gen/ is a stale/placeholder kernel.
|
||||
FRONTEND_SERVER="$HOST_TOOLS/dart-sdk/bin/snapshots/frontend_server_aot.dart.snapshot"
|
||||
if [ ! -f "$FRONTEND_SERVER" ]; then
|
||||
FRONTEND_SERVER="$HOST_TOOLS/gen/frontend_server_aot.dart.snapshot"
|
||||
fi
|
||||
"$HOST_TOOLS/dart-sdk/bin/dartaotruntime" \
|
||||
FRONTEND_SERVER=$(ResolveFrontendServer "$HOST_TOOLS" "$DART_AOT_RUNTIME") || return 1
|
||||
"$DART_AOT_RUNTIME" \
|
||||
"$FRONTEND_SERVER" \
|
||||
--sdk-root "$HOST_TOOLS/flutter_patched_sdk" \
|
||||
--aot --tfa --target=flutter \
|
||||
|
||||
Reference in New Issue
Block a user