From 8affbb7af62ff5b8f541969db6e592761d9e56df Mon Sep 17 00:00:00 2001
From: edde746 <86283021+edde746@users.noreply.github.com>
Date: Wed, 24 Jun 2026 18:25:02 +0200
Subject: [PATCH] fix(tvos): align embedded framework minimum os
---
tvos/Flutter/AppFrameworkInfo.plist | 2 +-
tvos/Runner.xcodeproj/project.pbxproj | 8 +-
.../xcshareddata/swiftpm/Package.resolved | 14 ++
tvos/scripts/xcode_appletv.sh | 169 +++++++++++++++++-
4 files changed, 182 insertions(+), 11 deletions(-)
create mode 100644 tvos/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
diff --git a/tvos/Flutter/AppFrameworkInfo.plist b/tvos/Flutter/AppFrameworkInfo.plist
index 8d4492f9..163000d8 100644
--- a/tvos/Flutter/AppFrameworkInfo.plist
+++ b/tvos/Flutter/AppFrameworkInfo.plist
@@ -21,6 +21,6 @@
CFBundleVersion
1.0
MinimumOSVersion
- 9.0
+ 14.0
diff --git a/tvos/Runner.xcodeproj/project.pbxproj b/tvos/Runner.xcodeproj/project.pbxproj
index 9c31fb9d..fe34a240 100644
--- a/tvos/Runner.xcodeproj/project.pbxproj
+++ b/tvos/Runner.xcodeproj/project.pbxproj
@@ -309,8 +309,8 @@
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
AE0235548DB8213618795857 /* Embed App Extensions */,
- 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
7C33FC680D67B3859BF9AE6C /* [CP] Embed Pods Frameworks */,
+ 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
);
buildRules = (
);
@@ -575,7 +575,7 @@
SDKROOT = appletvos;
SUPPORTED_PLATFORMS = "appletvos appletvsimulator";
TARGETED_DEVICE_FAMILY = 3;
- TVOS_DEPLOYMENT_TARGET = 13.0;
+ TVOS_DEPLOYMENT_TARGET = 14.0;
VALIDATE_PRODUCT = YES;
};
name = Profile;
@@ -760,7 +760,7 @@
ONLY_ACTIVE_ARCH = YES;
SDKROOT = appletvos;
TARGETED_DEVICE_FAMILY = 3;
- TVOS_DEPLOYMENT_TARGET = 13.0;
+ TVOS_DEPLOYMENT_TARGET = 14.0;
};
name = Debug;
};
@@ -812,7 +812,7 @@
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
TARGETED_DEVICE_FAMILY = 3;
- TVOS_DEPLOYMENT_TARGET = 13.0;
+ TVOS_DEPLOYMENT_TARGET = 14.0;
VALIDATE_PRODUCT = YES;
};
name = Release;
diff --git a/tvos/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/tvos/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
new file mode 100644
index 00000000..c1529bef
--- /dev/null
+++ b/tvos/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -0,0 +1,14 @@
+{
+ "originHash" : "c535d3cdb62bd4e11e67f3c9e68290fa7ee9306634c5f56d2b0396eb75ed41ee",
+ "pins" : [
+ {
+ "identity" : "mpvkit",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/edde746/MPVKit",
+ "state" : {
+ "revision" : "245b69dce22156e81b79c84cfcb748358b674e47"
+ }
+ }
+ ],
+ "version" : 3
+}
diff --git a/tvos/scripts/xcode_appletv.sh b/tvos/scripts/xcode_appletv.sh
index b5e1739b..dae2625e 100755
--- a/tvos/scripts/xcode_appletv.sh
+++ b/tvos/scripts/xcode_appletv.sh
@@ -100,6 +100,161 @@ ValidatePlistVersion() {
fi
}
+CopyAppFrameworkInfoPlist() {
+ local plist="$1"
+ local minimum_os_version="${2:-${TVOS_DEPLOYMENT_TARGET:-}}"
+
+ if [[ -z "$minimum_os_version" ]]; then
+ echo " └─ERROR: TVOS_DEPLOYMENT_TARGET is not set for App.framework Info.plist"
+ return 1
+ fi
+
+ cp "$PROJECT_DIR/scripts/Info.plist" "$plist"
+ SetPlistString "$plist" MinimumOSVersion "$minimum_os_version"
+}
+
+ValidateMinimumOSVersion() {
+ local plist="$1"
+ local label="$2"
+ local expected="$3"
+ local current=""
+
+ current="$(/usr/libexec/PlistBuddy -c "Print :MinimumOSVersion" "$plist" 2>/dev/null || true)"
+ if [[ "$current" != "$expected" ]]; then
+ echo " └─ERROR: $label MinimumOSVersion is $current, expected $expected"
+ return 1
+ fi
+}
+
+VersionLessThan() {
+ awk -v lhs="$1" -v rhs="$2" '
+ BEGIN {
+ split(lhs, left, ".")
+ split(rhs, right, ".")
+ for (i = 1; i <= 3; i++) {
+ l = left[i] == "" ? 0 : left[i] + 0
+ r = right[i] == "" ? 0 : right[i] + 0
+ if (l < r) exit 0
+ if (l > r) exit 1
+ }
+ exit 1
+ }
+ '
+}
+
+FrameworkExecutable() {
+ local framework="$1"
+ local plist="$framework/Info.plist"
+ local executable_name=""
+
+ executable_name="$(/usr/libexec/PlistBuddy -c "Print :CFBundleExecutable" "$plist" 2>/dev/null || true)"
+ if [[ -z "$executable_name" ]]; then
+ executable_name="$(basename "$framework" .framework)"
+ fi
+
+ printf "%s/%s\n" "$framework" "$executable_name"
+}
+
+MachOMinimumOSVersion() {
+ local executable="$1"
+
+ otool -l "$executable" 2>/dev/null | awk '
+ $1 == "cmd" && ($2 == "LC_BUILD_VERSION" || $2 == "LC_VERSION_MIN_APPLETVOS" || $2 == "LC_VERSION_MIN_IPHONEOS") {
+ in_version_command = 1
+ next
+ }
+ in_version_command && ($1 == "minos" || $1 == "version") {
+ print $2
+ exit
+ }
+ $1 == "cmd" {
+ in_version_command = 0
+ }
+ '
+}
+
+NormalizeFrameworkMinimumOSVersion() {
+ local framework="$1"
+ local app_minimum_os_version="$2"
+ local executable=""
+ local macho_minimum_os_version=""
+ local plist_minimum_os_version="$app_minimum_os_version"
+
+ executable="$(FrameworkExecutable "$framework")"
+ if [[ ! -f "$executable" ]]; then
+ echo " └─ERROR: framework executable missing: $executable"
+ return 1
+ fi
+
+ macho_minimum_os_version="$(MachOMinimumOSVersion "$executable")"
+ if [[ -n "$macho_minimum_os_version" ]] && VersionLessThan "$plist_minimum_os_version" "$macho_minimum_os_version"; then
+ plist_minimum_os_version="$macho_minimum_os_version"
+ fi
+
+ SetPlistString "$framework/Info.plist" MinimumOSVersion "$plist_minimum_os_version"
+ ValidateMinimumOSVersion "$framework/Info.plist" "$(basename "$framework")" "$plist_minimum_os_version"
+}
+
+CodeSignFramework() {
+ local framework="$1"
+ local executable=""
+
+ executable="$(FrameworkExecutable "$framework")"
+ if [[ ! -f "$executable" ]]; then
+ echo " └─ERROR: framework executable missing: $executable"
+ return 1
+ fi
+
+ if [[ "${PLATFORM_NAME:-}" != "appletvsimulator" && -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" && "${CODE_SIGNING_ALLOWED:-YES}" != "NO" ]]; then
+ codesign --force --verbose --sign "${EXPANDED_CODE_SIGN_IDENTITY}" -- "$framework"
+ fi
+}
+
+EmbedFlutterFrameworks() {
+ if [[ "${TARGET_NAME:-}" != "Runner" ]]; then
+ return 0
+ fi
+
+ local minimum_os_version="${TVOS_DEPLOYMENT_TARGET:-}"
+ local runner_plist="$TARGET_BUILD_DIR/$WRAPPER_NAME/Info.plist"
+ if [[ -z "$minimum_os_version" && -f "$runner_plist" ]]; then
+ minimum_os_version="$(/usr/libexec/PlistBuddy -c "Print :MinimumOSVersion" "$runner_plist" 2>/dev/null || true)"
+ fi
+
+ if [[ -z "$minimum_os_version" ]]; then
+ echo " └─ERROR: could not resolve tvOS MinimumOSVersion for embedded Flutter frameworks"
+ return 1
+ fi
+
+ local app_frameworks_dir="$TARGET_BUILD_DIR/$WRAPPER_NAME/Frameworks"
+ mkdir -p "$app_frameworks_dir"
+
+ local framework=""
+ for framework in App.framework Flutter.framework; do
+ local source="$BUILT_PRODUCTS_DIR/$framework"
+ local destination="$app_frameworks_dir/$framework"
+
+ if [[ ! -d "$source" ]]; then
+ echo " └─ERROR: built framework missing: $source"
+ return 1
+ fi
+
+ NormalizeFrameworkMinimumOSVersion "$source" "$minimum_os_version"
+ rm -rf "$destination"
+ cp -R "$source" "$app_frameworks_dir"
+ NormalizeFrameworkMinimumOSVersion "$destination" "$minimum_os_version"
+ done
+
+ for framework in "$app_frameworks_dir"/*.framework; do
+ if [[ ! -d "$framework" ]]; then
+ continue
+ fi
+
+ NormalizeFrameworkMinimumOSVersion "$framework" "$minimum_os_version"
+ CodeSignFramework "$framework"
+ done
+}
+
SyncRunnerVersion() {
ReadPubspecVersion
@@ -129,6 +284,8 @@ SyncRunnerVersion() {
SetPlistString "$top_shelf_plist" CFBundleVersion "$FLUTTER_BUILD_NUMBER"
ValidatePlistVersion "$top_shelf_plist" "TopShelfExtension"
fi
+
+ EmbedFlutterFrameworks
}
EngineOutputExists() {
@@ -173,8 +330,8 @@ BuildAppDebug() {
rm -rf "$OUTDIR/Flutter.framework"
cp -R "$DEVICE_TOOLS/Flutter.framework" "$OUTDIR"
# The engine tarball's Flutter.framework/Info.plist declares MinimumOSVersion
- # 13.0 but the binary is linked with minos=14.0. Apple's validator compares
- # both against the host app — align the plist to 14.0 so the upload passes.
+ # 13.0 but the binary is linked with the resolved deployment target. Apple's
+ # validator compares both against the host app, so keep the plist aligned.
plutil -replace MinimumOSVersion -string "$TVOS_DEPLOYMENT_TARGET" "$OUTDIR/Flutter.framework/Info.plist"
@@ -268,7 +425,7 @@ BuildAppDebug() {
strip "$OUTDIR/App.framework/App"
echo " └─copy frameworks"
- cp "$PROJECT_DIR/scripts/Info.plist" "$OUTDIR/App.framework/Info.plist"
+ CopyAppFrameworkInfoPlist "$OUTDIR/App.framework/Info.plist" "$tvos_deployment_target"
# Two destinations:
# 1. BUILT_PRODUCTS_DIR — Swift/linker search path. For plain builds this
@@ -340,8 +497,8 @@ BuildAppRelease() {
rm -rf "$OUTDIR/Flutter.framework"
cp -R "$DEVICE_TOOLS/Flutter.framework" "$OUTDIR"
# The engine tarball's Flutter.framework/Info.plist declares MinimumOSVersion
- # 13.0 but the binary is linked with minos=14.0. Apple's validator compares
- # both against the host app — align the plist to 14.0 so the upload passes.
+ # 13.0 but the binary is linked with the resolved deployment target. Apple's
+ # validator compares both against the host app, so keep the plist aligned.
plutil -replace MinimumOSVersion -string "$TVOS_DEPLOYMENT_TARGET" "$OUTDIR/Flutter.framework/Info.plist"
tvos_deployment_target="$TVOS_DEPLOYMENT_TARGET"
@@ -423,7 +580,7 @@ BuildAppRelease() {
strip "$OUTDIR/App.framework/App"
- cp "$PROJECT_DIR/scripts/Info.plist" "$OUTDIR/App.framework/Info.plist"
+ CopyAppFrameworkInfoPlist "$OUTDIR/App.framework/Info.plist" "$tvos_deployment_target"
echo " └─copy frameworks"
# BUILT_PRODUCTS_DIR = linker search path. DO NOT use TARGET_BUILD_DIR