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:
edde746
2026-07-24 03:56:40 +02:00
parent b41fb4fe75
commit 09656fa4d3
63 changed files with 5146 additions and 560 deletions
+158 -21
View File
@@ -13,30 +13,39 @@
35DB0C8FEF635A3BCA0B722A /* PackageInfoPlusPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9426EFA282CDA8E0E98EEE9 /* PackageInfoPlusPlugin.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
5C71F5F7B33075F2B007825B /* MpvPlayerPluginShared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73645904F226A24585A092CE /* MpvPlayerPluginShared.swift */; };
7A11A7C50113007825B00A01 /* AtmosProbePlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A11A7C50113007825B00A02 /* AtmosProbePlugin.swift */; };
691577F0EB3F4EB1A5160280 /* MpvPlayerCoreBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = D52A3BDA46E79969EA1DF3AC /* MpvPlayerCoreBase.swift */; };
B1D51A6A2F00110000000014 /* MpvAudioPlayerCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1D51A6A2F00110000000015 /* MpvAudioPlayerCore.swift */; };
B1D51A6A2F00110000000016 /* MpvAudioPlayerPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1D51A6A2F00110000000017 /* MpvAudioPlayerPlugin.swift */; };
6F3C0DD6F2F8DA14E7E2F386 /* MpvPlayerCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = A12B8610AE5D580077264851 /* MpvPlayerCore.swift */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
7A11A7C50113007825B00A01 /* AtmosProbePlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A11A7C50113007825B00A02 /* AtmosProbePlugin.swift */; };
81325C1CD13794375A81AC02 /* messages.g.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34CD411CCD84E381C4BF4C1B /* messages.g.swift */; };
81F08E404EBEB19B00FF148D /* ConnectivityPlusPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67C3193A50DFDECE2B92D075 /* ConnectivityPlusPlugin.swift */; };
89CF031971F407719E4B5DD8 /* TvosEventDeliveryCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F56950138FF220CA079EB3 /* TvosEventDeliveryCoordinator.swift */; };
8E5EED3DDAC9455D4DAA9776 /* MpvPlayerPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D7A998830EDC8F77BF521D1 /* MpvPlayerPlugin.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
A7596F780E93AF0BE3838A26 /* ConnectivityProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CCA7E80F3A99D8759F2E59F /* ConnectivityProvider.swift */; };
AA34E3E5872B3792D6959EAA /* MpvPipController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2635E12EB9322B151EE5127 /* MpvPipController.swift */; };
B1D51A6A2F00110000000014 /* MpvAudioPlayerCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1D51A6A2F00110000000015 /* MpvAudioPlayerCore.swift */; };
B1D51A6A2F00110000000016 /* MpvAudioPlayerPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1D51A6A2F00110000000017 /* MpvAudioPlayerPlugin.swift */; };
BDD87DA7F5C435F9DBB8F9BE /* TopShelfExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 035F0D5A5E54BE7AD9AFA23C /* TopShelfExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
C6A15611158B29B0FF43A960 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 824D2C68D1F1206932E118C2 /* Pods_Runner.framework */; };
CD1C0534948840272E58248E /* PathMonitorConnectivityProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AEF79DFBD1D80F00AB39CDA /* PathMonitorConnectivityProvider.swift */; };
D2004D7BB4A40340AB7A01E0 /* TvosEventDeliveryCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE6C8125B201A8BC3261AE2B /* TvosEventDeliveryCoordinatorTests.swift */; };
D2A548D9DE1A0F319B30B74C /* SystemShelfPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0760C00EDC55A4D718BCB406 /* SystemShelfPlugin.swift */; };
EA0F4264E7B912702C490109 /* MPVKit in Frameworks */ = {isa = PBXBuildFile; productRef = EA0F4263E7B912702C490108 /* MPVKit */; };
E3DEAD2AAFC347A2E55AC0F7 /* SharedPreferencesPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0455EBA0EF4A61D3B71D2D7 /* SharedPreferencesPlugin.swift */; };
E79A4474D308631AFA59CAE7 /* DeviceInfoPlusPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E1F41676FEF1AB57076F8B9 /* DeviceInfoPlusPlugin.swift */; };
EA0F4264E7B912702C490109 /* MPVKit in Frameworks */ = {isa = PBXBuildFile; productRef = EA0F4263E7B912702C490108 /* MPVKit */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
312B21E3C26DC9F7E1AC1F8C /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
proxyType = 1;
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
remoteInfo = Runner;
};
3E4E59C5E29F44BA64BEB559 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
@@ -75,18 +84,20 @@
04DD35536DEE7C27FFA53862 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
0760C00EDC55A4D718BCB406 /* SystemShelfPlugin.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SystemShelfPlugin.swift; sourceTree = "<group>"; };
0C25F4F2367A30B47E945AD6 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
1B17916D7270A141E3AC7B5D /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
25B7925EFD8A2C1C7EB667D5 /* TVServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = TVServices.framework; path = System/Library/Frameworks/TVServices.framework; sourceTree = SDKROOT; };
34CD411CCD84E381C4BF4C1B /* messages.g.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = messages.g.swift; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
3CCA7E80F3A99D8759F2E59F /* ConnectivityProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ConnectivityProvider.swift; sourceTree = "<group>"; };
420881FB6A648A2AFD39FFF2 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
5E1F41676FEF1AB57076F8B9 /* DeviceInfoPlusPlugin.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DeviceInfoPlusPlugin.swift; sourceTree = "<group>"; };
66F56950138FF220CA079EB3 /* TvosEventDeliveryCoordinator.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TvosEventDeliveryCoordinator.swift; sourceTree = "<group>"; };
67C3193A50DFDECE2B92D075 /* ConnectivityPlusPlugin.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ConnectivityPlusPlugin.swift; sourceTree = "<group>"; };
6AEF79DFBD1D80F00AB39CDA /* PathMonitorConnectivityProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PathMonitorConnectivityProvider.swift; sourceTree = "<group>"; };
73645904F226A24585A092CE /* MpvPlayerPluginShared.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = MpvPlayerPluginShared.swift; path = ../shared/apple/MpvPlayer/MpvPlayerPluginShared.swift; sourceTree = "<source_root>"; };
7A11A7C50113007825B00A02 /* AtmosProbePlugin.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AtmosProbePlugin.swift; path = ../shared/apple/AtmosProbe/AtmosProbePlugin.swift; sourceTree = "<source_root>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7A11A7C50113007825B00A02 /* AtmosProbePlugin.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AtmosProbePlugin.swift; path = ../shared/apple/AtmosProbe/AtmosProbePlugin.swift; sourceTree = "<source_root>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
824D2C68D1F1206932E118C2 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
9165AF55B967D8845D042FE7 /* TopShelfExtension.entitlements */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.entitlements; path = TopShelfExtension.entitlements; sourceTree = "<group>"; };
@@ -101,14 +112,15 @@
9D7A998830EDC8F77BF521D1 /* MpvPlayerPlugin.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = MpvPlayerPlugin.swift; path = ../ios/Runner/MpvPlayer/MpvPlayerPlugin.swift; sourceTree = "<source_root>"; };
A12B8610AE5D580077264851 /* MpvPlayerCore.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = MpvPlayerCore.swift; path = ../ios/Runner/MpvPlayer/MpvPlayerCore.swift; sourceTree = "<source_root>"; };
A2635E12EB9322B151EE5127 /* MpvPipController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = MpvPipController.swift; path = ../ios/Runner/MpvPlayer/MpvPipController.swift; sourceTree = "<source_root>"; };
B1D51A6A2F00110000000015 /* MpvAudioPlayerCore.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = MpvAudioPlayerCore.swift; path = ../shared/apple/MpvPlayer/MpvAudioPlayerCore.swift; sourceTree = "<source_root>"; };
B1D51A6A2F00110000000017 /* MpvAudioPlayerPlugin.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = MpvAudioPlayerPlugin.swift; path = ../shared/apple/MpvPlayer/MpvAudioPlayerPlugin.swift; sourceTree = "<source_root>"; };
BBCB49C8AE9E90DEF97A87CA /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
C0455EBA0EF4A61D3B71D2D7 /* SharedPreferencesPlugin.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SharedPreferencesPlugin.swift; sourceTree = "<group>"; };
D41AA251EF365516E2AC5287 /* PathProviderPlugin.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PathProviderPlugin.swift; sourceTree = "<group>"; };
B1D51A6A2F00110000000015 /* MpvAudioPlayerCore.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = MpvAudioPlayerCore.swift; path = ../shared/apple/MpvPlayer/MpvAudioPlayerCore.swift; sourceTree = "<source_root>"; };
B1D51A6A2F00110000000017 /* MpvAudioPlayerPlugin.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = MpvAudioPlayerPlugin.swift; path = ../shared/apple/MpvPlayer/MpvAudioPlayerPlugin.swift; sourceTree = "<source_root>"; };
D52A3BDA46E79969EA1DF3AC /* MpvPlayerCoreBase.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = MpvPlayerCoreBase.swift; path = ../shared/apple/MpvPlayer/MpvPlayerCoreBase.swift; sourceTree = "<source_root>"; };
F2F829B3F190657106F66379 /* TopShelfProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TopShelfProvider.swift; sourceTree = "<group>"; };
F9426EFA282CDA8E0E98EEE9 /* PackageInfoPlusPlugin.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PackageInfoPlusPlugin.swift; sourceTree = "<group>"; };
FE6C8125B201A8BC3261AE2B /* TvosEventDeliveryCoordinatorTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TvosEventDeliveryCoordinatorTests.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -120,6 +132,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
5E27C9DB84FD0E86AD0F4664 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@@ -188,6 +207,7 @@
children = (
824D2C68D1F1206932E118C2 /* Pods_Runner.framework */,
25B7925EFD8A2C1C7EB667D5 /* TVServices.framework */,
6D9369A1A01A73D85CAEC2A6 /* tvOS */,
);
name = Frameworks;
sourceTree = "<group>";
@@ -201,6 +221,13 @@
path = device_info_plus;
sourceTree = "<group>";
};
6D9369A1A01A73D85CAEC2A6 /* tvOS */ = {
isa = PBXGroup;
children = (
);
name = tvOS;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
@@ -221,6 +248,7 @@
53CDD29681161166962B9FA5 /* Pods */,
5833EC5B503BBB4E370BA1B7 /* Frameworks */,
47C2D3F50A23DE33DE92F592 /* TopShelfExtension */,
F9BDF6E09E3B9347861D2A50 /* RunnerTests */,
);
sourceTree = "<group>";
};
@@ -229,6 +257,7 @@
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
035F0D5A5E54BE7AD9AFA23C /* TopShelfExtension.appex */,
1B17916D7270A141E3AC7B5D /* RunnerTests.xctest */,
);
name = Products;
sourceTree = "<group>";
@@ -246,6 +275,7 @@
D58B788B11920D95BC7D750C /* Plugins */,
0760C00EDC55A4D718BCB406 /* SystemShelfPlugin.swift */,
937C0D45D6114EF1E957F5F6 /* Runner.entitlements */,
66F56950138FF220CA079EB3 /* TvosEventDeliveryCoordinator.swift */,
);
path = Runner;
sourceTree = "<group>";
@@ -288,6 +318,15 @@
path = path_provider;
sourceTree = "<group>";
};
F9BDF6E09E3B9347861D2A50 /* RunnerTests */ = {
isa = PBXGroup;
children = (
FE6C8125B201A8BC3261AE2B /* TvosEventDeliveryCoordinatorTests.swift */,
);
name = RunnerTests;
path = RunnerTests;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@@ -336,6 +375,24 @@
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
B03B26B6860DAA4B4861ABE8 /* RunnerTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = E0F018DBFAD5E036F8B3DED3 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
877FBDFAE7193C414BFC7581 /* Sources */,
5E27C9DB84FD0E86AD0F4664 /* Frameworks */,
BA52311F6E3823811234FBB7 /* Resources */,
);
buildRules = (
);
dependencies = (
B904E5D4A048ADFF0B26C3B3 /* PBXTargetDependency */,
);
name = RunnerTests;
productName = RunnerTests;
productReference = 1B17916D7270A141E3AC7B5D /* RunnerTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
@@ -369,6 +426,7 @@
targets = (
97C146ED1CF9000F007C117D /* Runner */,
63C63C245D7F38BFF091CEC3 /* TopShelfExtension */,
B03B26B6860DAA4B4861ABE8 /* RunnerTests */,
);
};
/* End PBXProject section */
@@ -392,6 +450,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
BA52311F6E3823811234FBB7 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
@@ -448,20 +513,6 @@
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
E2B5A6D75C9F4D1B9E8C7A63 /* Sync Version */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Sync Version";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/bash \"$SOURCE_ROOT/scripts/xcode_appletv.sh\" sync_version\n";
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@@ -476,9 +527,31 @@
shellPath = /bin/sh;
shellScript = "#/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n/bin/bash \"$SOURCE_ROOT/scripts/xcode_appletv.sh\" build\n";
};
E2B5A6D75C9F4D1B9E8C7A63 /* Sync Version */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Sync Version";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/bash \"$SOURCE_ROOT/scripts/xcode_appletv.sh\" sync_version\n";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
877FBDFAE7193C414BFC7581 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
D2004D7BB4A40340AB7A01E0 /* TvosEventDeliveryCoordinatorTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@@ -501,6 +574,7 @@
A7596F780E93AF0BE3838A26 /* ConnectivityProvider.swift in Sources */,
CD1C0534948840272E58248E /* PathMonitorConnectivityProvider.swift in Sources */,
D2A548D9DE1A0F319B30B74C /* SystemShelfPlugin.swift in Sources */,
89CF031971F407719E4B5DD8 /* TvosEventDeliveryCoordinator.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -515,6 +589,12 @@
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
B904E5D4A048ADFF0B26C3B3 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
name = Runner;
target = 97C146ED1CF9000F007C117D /* Runner */;
targetProxy = 312B21E3C26DC9F7E1AC1F8C /* PBXContainerItemProxy */;
};
F0399FC35D3A6ED67E74810D /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
name = TopShelfExtension;
@@ -692,6 +772,22 @@
};
name = Profile;
};
4F937E75F619E4ABE22B1F17 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
GENERATE_INFOPLIST_FILE = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.edde746.plezy.RunnerTests;
SDKROOT = appletvos;
SUPPORTED_PLATFORMS = "appletvos appletvsimulator";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 3;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner";
TVOS_DEPLOYMENT_TARGET = 14.0;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
65F6020FE0DF48006BADE49C /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB31CF90195004384FC /* Generated.xcconfig */;
@@ -907,6 +1003,37 @@
};
name = Release;
};
AE855FD4D5DF4B132727705D /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
GENERATE_INFOPLIST_FILE = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.edde746.plezy.RunnerTests;
SDKROOT = appletvos;
SUPPORTED_PLATFORMS = "appletvos appletvsimulator";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 3;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner";
TVOS_DEPLOYMENT_TARGET = 14.0;
VALIDATE_PRODUCT = YES;
};
name = Profile;
};
CD5AF092D8A44891EB23CECC /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
GENERATE_INFOPLIST_FILE = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.edde746.plezy.RunnerTests;
SDKROOT = appletvos;
SUPPORTED_PLATFORMS = "appletvos appletvsimulator";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 3;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner";
TVOS_DEPLOYMENT_TARGET = 14.0;
};
name = Debug;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
@@ -940,6 +1067,16 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
E0F018DBFAD5E036F8B3DED3 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
4F937E75F619E4ABE22B1F17 /* Release */,
CD5AF092D8A44891EB23CECC /* Debug */,
AE855FD4D5DF4B132727705D /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
@@ -51,6 +51,16 @@
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B03B26B6860DAA4B4861ABE8"
BuildableName = "RunnerTests.xctest"
BlueprintName = "RunnerTests"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
@@ -0,0 +1,105 @@
import Foundation
/// Keeps tvOS launcher events single-shot without suppressing a deliberate
/// second tap forever. All access is confined to the main thread by the host.
final class TvosDeepLinkDeliveryCoordinator {
struct Event: Equatable {
let contentId: String
let receivedAt: TimeInterval
var flutterArguments: [String: String] { ["contentId": contentId] }
fileprivate let sequence: UInt64
}
enum Receipt: Equatable {
case retained
case duplicate
case deliver(Event)
}
private let deduplicationWindow: TimeInterval
private let now: () -> TimeInterval
private var sequence: UInt64 = 0
private var liveDeliveryEnabled = false
private var pending: Event?
private var inFlight: Event?
private var lastDelivered: Event?
init(
deduplicationWindow: TimeInterval = 2,
now: @escaping () -> TimeInterval = { ProcessInfo.processInfo.systemUptime }
) {
precondition(deduplicationWindow >= 0)
self.deduplicationWindow = deduplicationWindow
self.now = now
}
/// A replacement Flutter engine must perform its initial read before live
/// delivery resumes. A launch event captured before registration is retained.
func bindEngine() {
liveDeliveryEnabled = false
inFlight = nil
}
func receive(contentId: String) -> Receipt {
let timestamp = now()
if let inFlight, isDuplicate(contentId: contentId, at: timestamp, of: inFlight) {
return .duplicate
}
if let pending, isDuplicate(contentId: contentId, at: timestamp, of: pending) {
// A retained failed delivery is retryable when no send is active.
return liveDeliveryEnabled && inFlight == nil ? beginDelivery(pending) : .duplicate
}
if let lastDelivered, isDuplicate(contentId: contentId, at: timestamp, of: lastDelivered) {
return .duplicate
}
sequence &+= 1
let event = Event(contentId: contentId, receivedAt: timestamp, sequence: sequence)
pending = event
guard liveDeliveryEnabled, inFlight == nil else { return .retained }
return beginDelivery(event)
}
/// Atomically transitions an engine from retained launch delivery to live
/// delivery and returns the one retained event, if any.
func beginLiveDelivery() -> Event? {
liveDeliveryEnabled = true
guard inFlight == nil, let pending else { return nil }
inFlight = pending
return pending
}
/// Clears a retained event only after Flutter reports successful delivery.
/// A newer retained event is returned so callers can deliver it next.
func complete(_ event: Event, succeeded: Bool) -> Event? {
guard inFlight == event else { return nil }
inFlight = nil
guard succeeded else { return nil }
lastDelivered = event
if pending == event {
pending = nil
}
guard liveDeliveryEnabled, let pending else { return nil }
return beginDelivery(pending).event
}
var retainedContentId: String? { pending?.contentId }
private func beginDelivery(_ event: Event) -> Receipt {
inFlight = event
return .deliver(event)
}
private func isDuplicate(contentId: String, at timestamp: TimeInterval, of event: Event) -> Bool {
contentId == event.contentId && timestamp - event.receivedAt <= deduplicationWindow
}
}
private extension TvosDeepLinkDeliveryCoordinator.Receipt {
var event: TvosDeepLinkDeliveryCoordinator.Event? {
if case let .deliver(event) = self { return event }
return nil
}
}
@@ -0,0 +1,89 @@
import XCTest
@testable import Runner
final class TvosEventDeliveryCoordinatorTests: XCTestCase {
func testColdLaunchTransitionsToLiveWithoutChangingFlutterEnvelope() {
var now: TimeInterval = 10
let coordinator = TvosDeepLinkDeliveryCoordinator(now: { now })
XCTAssertEqual(coordinator.receive(contentId: "server:item"), .retained)
let launchEvent = try! XCTUnwrap(coordinator.beginLiveDelivery())
XCTAssertEqual(launchEvent.contentId, "server:item")
XCTAssertEqual(launchEvent.flutterArguments, ["contentId": "server:item"])
XCTAssertNil(coordinator.complete(launchEvent, succeeded: true))
XCTAssertNil(coordinator.retainedContentId)
now += 0.5
XCTAssertEqual(coordinator.receive(contentId: "server:item"), .duplicate)
}
func testFailedLiveDeliveryRemainsRetainedAndRetryable() {
var now: TimeInterval = 20
let coordinator = TvosDeepLinkDeliveryCoordinator(now: { now })
XCTAssertNil(coordinator.beginLiveDelivery())
guard case let .deliver(first) = coordinator.receive(contentId: "server:item") else {
return XCTFail("Expected live delivery")
}
XCTAssertNil(coordinator.complete(first, succeeded: false))
XCTAssertEqual(coordinator.retainedContentId, "server:item")
now += 0.1
guard case let .deliver(retry) = coordinator.receive(contentId: "server:item") else {
return XCTFail("Expected failed delivery to retry")
}
XCTAssertEqual(retry, first)
XCTAssertNil(coordinator.complete(retry, succeeded: true))
XCTAssertNil(coordinator.retainedContentId)
}
func testDeduplicationWindowExpiresForASecondDeliberateTap() {
var now: TimeInterval = 30
let coordinator = TvosDeepLinkDeliveryCoordinator(deduplicationWindow: 2, now: { now })
XCTAssertNil(coordinator.beginLiveDelivery())
guard case let .deliver(first) = coordinator.receive(contentId: "server:item") else {
return XCTFail("Expected first delivery")
}
_ = coordinator.complete(first, succeeded: true)
now += 1.99
XCTAssertEqual(coordinator.receive(contentId: "server:item"), .duplicate)
now += 0.02
guard case let .deliver(second) = coordinator.receive(contentId: "server:item") else {
return XCTFail("Expected delivery after bounded window")
}
XCTAssertNotEqual(second, first)
}
func testInFlightDeliveryCoalescesDuplicateAndThenDeliversNewestEvent() {
var now: TimeInterval = 40
let coordinator = TvosDeepLinkDeliveryCoordinator(now: { now })
XCTAssertNil(coordinator.beginLiveDelivery())
guard case let .deliver(first) = coordinator.receive(contentId: "server:first") else {
return XCTFail("Expected first delivery")
}
now += 0.1
XCTAssertEqual(coordinator.receive(contentId: "server:first"), .duplicate)
XCTAssertEqual(coordinator.receive(contentId: "server:second"), .retained)
let second = try! XCTUnwrap(coordinator.complete(first, succeeded: true))
XCTAssertEqual(second.contentId, "server:second")
XCTAssertNil(coordinator.complete(second, succeeded: true))
XCTAssertNil(coordinator.retainedContentId)
}
func testEngineRebindRetainsLaunchEventAndRequiresInitialReadAgain() {
let coordinator = TvosDeepLinkDeliveryCoordinator(now: { 50 })
XCTAssertNil(coordinator.beginLiveDelivery())
guard case let .deliver(event) = coordinator.receive(contentId: "server:item") else {
return XCTFail("Expected live delivery")
}
XCTAssertNil(coordinator.complete(event, succeeded: false))
coordinator.bindEngine()
XCTAssertEqual(coordinator.receive(contentId: "server:item"), .duplicate)
XCTAssertEqual(coordinator.beginLiveDelivery()?.contentId, "server:item")
}
}
+1
View File
@@ -0,0 +1 @@
db7b9740fbc38dd7ecead775f2c04cbfced2cab95717f2ed1d00d1f0f4438a55
+31 -13
View File
@@ -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.
+56
View File
@@ -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'
+58 -11
View File
@@ -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 \