From d6c17ed45939fff5fe0d45315c9c94238a3f3719 Mon Sep 17 00:00:00 2001 From: Jenn Magder Date: Wed, 8 Mar 2023 16:32:25 -0800 Subject: [PATCH] t st:wq! Build iOS and macOS Obj-C with -fapplication-extension flag Linker flag Find the App.framework asset URL from the app extension Remove load draft extension safe built flag format gn format macos test fix clang tidy rename flag add linker flag format draft format: add LINE EOF fix typo format fix run search path fix copying fix build.gn format format --- common/config.gni | 6 +- shell/platform/darwin/ios/BUILD.gn | 31 ++++++++- .../framework/Headers/FlutterAppDelegate.h | 1 + .../ios/framework/Headers/FlutterPlugin.h | 3 +- .../FlutterPluginAppLifeCycleDelegate.h | 1 + .../framework/Source/FlutterDartProject.mm | 65 +++++++++++++------ .../ios/framework/Source/FlutterEngine.mm | 6 +- .../framework/Source/FlutterPlatformPlugin.mm | 10 +++ .../framework/Source/FlutterViewController.mm | 41 +++++++++++- shell/platform/darwin/macos/BUILD.gn | 13 +++- .../framework/Source/FlutterUmbrellaImport.m | 11 ++++ tools/gn | 11 ++++ 12 files changed, 167 insertions(+), 32 deletions(-) create mode 100644 shell/platform/darwin/macos/framework/Source/FlutterUmbrellaImport.m diff --git a/common/config.gni b/common/config.gni index 12a4141cb7c82..809377b5fdfd6 100644 --- a/common/config.gni +++ b/common/config.gni @@ -22,6 +22,9 @@ declare_args() { # Whether to include backtrace support. enable_backtrace = true + + # Whether to include --fapplication-extension when build iOS framework. + darwin_extension_safe = false } # feature_defines_list --------------------------------------------------------- @@ -61,10 +64,9 @@ if (is_ios || is_mac) { "-Werror=overriding-method-mismatch", "-Werror=undeclared-selector", ] - if (is_mac) { + if (darwin_extension_safe || is_mac) { flutter_cflags_objc += [ "-fapplication-extension" ] } - flutter_cflags_objcc = flutter_cflags_objc flutter_cflags_objc_arc = flutter_cflags_objc + [ "-fobjc-arc" ] flutter_cflags_objcc_arc = flutter_cflags_objc_arc diff --git a/shell/platform/darwin/ios/BUILD.gn b/shell/platform/darwin/ios/BUILD.gn index 4acf7e2bcf062..bfdb890003da1 100644 --- a/shell/platform/darwin/ios/BUILD.gn +++ b/shell/platform/darwin/ios/BUILD.gn @@ -13,6 +13,7 @@ import("//flutter/shell/platform/darwin/common/framework_shared.gni") import("//flutter/testing/testing.gni") _flutter_framework_dir = "$root_out_dir/Flutter.framework" +_flutter_xcframework_dir = "$root_out_dir/Flutter.xcframework" shell_gpu_configuration("ios_gpu_configuration") { enable_software = true @@ -44,6 +45,9 @@ source_set("flutter_framework_source_arc") { cflags_objcc = flutter_cflags_objcc_arc defines = [ "FLUTTER_FRAMEWORK=1" ] + if (darwin_extension_safe) { + defines += [ "APPLICATION_EXTENSION_API_ONLY=1" ] + } allow_circular_includes_from = [ ":flutter_framework_source" ] deps = [ ":flutter_framework_source", @@ -153,6 +157,9 @@ source_set("flutter_framework_source") { sources += _flutter_framework_headers defines = [ "FLUTTER_FRAMEWORK=1" ] + if (darwin_extension_safe) { + defines += [ "APPLICATION_EXTENSION_API_ONLY=1" ] + } if (shell_enable_metal) { sources += [ @@ -321,6 +328,10 @@ shared_library("create_flutter_framework_dylib") { ldflags = [ "-Wl,-install_name,@rpath/Flutter.framework/Flutter" ] + if (darwin_extension_safe) { + ldflags += [ "-fapplication-extension" ] + } + public = _flutter_framework_headers deps = [ @@ -411,10 +422,24 @@ copy("copy_license") { shared_library("copy_and_verify_framework_module") { framework_search_path = rebase_path("$root_out_dir") visibility = [ ":*" ] - cflags_objc = [ "-F$framework_search_path" ] + cflags_objc = [ + "-F$framework_search_path", + "-fapplication-extension", + ] + + if (darwin_extension_safe) { + ldflags = [ + "-F$framework_search_path", + "-fapplication-extension", + "-Xlinker", + "-fatal_warnings", + ] + frameworks = [ "Flutter.framework" ] + deps = [ ":copy_dylib" ] + } sources = [ "framework/Source/FlutterUmbrellaImport.m" ] - deps = [ + deps += [ ":copy_framework_headers", ":copy_framework_info_plist", ":copy_framework_module_map", @@ -439,7 +464,7 @@ group("universal_flutter_framework") { action("flutter_framework") { script = "//flutter/sky/tools/create_xcframework.py" - outputs = [ "$root_out_dir/Flutter.xcframework" ] + outputs = [ "$_flutter_xcframework_dir" ] args = [ "--frameworks", rebase_path("$_flutter_framework_dir"), diff --git a/shell/platform/darwin/ios/framework/Headers/FlutterAppDelegate.h b/shell/platform/darwin/ios/framework/Headers/FlutterAppDelegate.h index ed7589a53c4ca..d4990a09532ae 100644 --- a/shell/platform/darwin/ios/framework/Headers/FlutterAppDelegate.h +++ b/shell/platform/darwin/ios/framework/Headers/FlutterAppDelegate.h @@ -24,6 +24,7 @@ * code as necessary from FlutterAppDelegate.mm. */ FLUTTER_DARWIN_EXPORT +NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions") @interface FlutterAppDelegate : UIResponder diff --git a/shell/platform/darwin/ios/framework/Headers/FlutterPlugin.h b/shell/platform/darwin/ios/framework/Headers/FlutterPlugin.h index b270414900692..524e68293c95a 100644 --- a/shell/platform/darwin/ios/framework/Headers/FlutterPlugin.h +++ b/shell/platform/darwin/ios/framework/Headers/FlutterPlugin.h @@ -353,7 +353,8 @@ typedef enum { * * @param delegate The receiving object, such as the plugin's main class. */ -- (void)addApplicationDelegate:(NSObject*)delegate; +- (void)addApplicationDelegate:(NSObject*)delegate + NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in plugins used in app extensions"); /** * Returns the file name for the given asset. diff --git a/shell/platform/darwin/ios/framework/Headers/FlutterPluginAppLifeCycleDelegate.h b/shell/platform/darwin/ios/framework/Headers/FlutterPluginAppLifeCycleDelegate.h index ba317f4214291..10cdf3fe8618e 100644 --- a/shell/platform/darwin/ios/framework/Headers/FlutterPluginAppLifeCycleDelegate.h +++ b/shell/platform/darwin/ios/framework/Headers/FlutterPluginAppLifeCycleDelegate.h @@ -13,6 +13,7 @@ NS_ASSUME_NONNULL_BEGIN * Propagates `UIAppDelegate` callbacks to registered plugins. */ FLUTTER_DARWIN_EXPORT +NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in plugins used in app extensions") @interface FlutterPluginAppLifeCycleDelegate : NSObject /** diff --git a/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm b/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm index 07334dbdd9fbb..54e5c2b0d3cff 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm @@ -32,6 +32,39 @@ static const char* kApplicationKernelSnapshotFileName = "kernel_blob.bin"; +NS_INLINE NSBundle* FLTFrameworkBundle() { + NSBundle* mainBundle = [NSBundle mainBundle]; + NSBundle* bundle = FLTFrameworkBundleWithIdentifier([FlutterDartProject defaultBundleIdentifier]); + + // App extension bundle is in Runner.app/PlugIns/Extension.appex. + if ([mainBundle.bundleURL.pathExtension isEqualToString:@"appex"]) { + // Up two levels. + NSBundle* appBundle = + [NSBundle bundleWithURL:mainBundle.bundleURL.URLByDeletingLastPathComponent + .URLByDeletingLastPathComponent]; + bundle = FLTFrameworkBundleInternal([FlutterDartProject defaultBundleIdentifier], + appBundle.privateFrameworksURL); + } + + if (bundle == nil) { + bundle = mainBundle; + } + + return bundle; +} + +NS_INLINE NSURL* FLTAssetsFromBundle(NSBundle* bundle) { + NSString* assetsPathFromInfoPlist = [bundle objectForInfoDictionaryKey:@"FLTAssetsPath"]; + NSString* flutterAssetsName = + assetsPathFromInfoPlist != nil ? assetsPathFromInfoPlist : @"flutter_assets"; + NSURL* assets = [bundle URLForResource:flutterAssetsName withExtension:nil]; + + if ([assets checkResourceIsReachableAndReturnError:NULL]) { + return assets; + } + return nil; +} + flutter::Settings FLTDefaultSettingsForBundle(NSBundle* bundle, NSProcessInfo* processInfoOrNil) { auto command_line = flutter::CommandLineFromNSProcessInfo(processInfoOrNil); @@ -46,10 +79,7 @@ bool hasExplicitBundle = bundle != nil; if (bundle == nil) { - bundle = FLTFrameworkBundleWithIdentifier([FlutterDartProject defaultBundleIdentifier]); - } - if (bundle == nil) { - bundle = mainBundle; + bundle = FLTFrameworkBundle(); } auto settings = flutter::SettingsFromCommandLine(command_line); @@ -122,29 +152,24 @@ // Checks to see if the flutter assets directory is already present. if (settings.assets_path.empty()) { - NSString* assetsName = [FlutterDartProject flutterAssetsName:bundle]; - NSString* assetsPath = [bundle pathForResource:assetsName ofType:@""]; + NSURL* assetsURL = FLTAssetsFromBundle(bundle); - if (assetsPath.length == 0) { - assetsPath = [mainBundle pathForResource:assetsName ofType:@""]; - } - - if (assetsPath.length == 0) { - NSLog(@"Failed to find assets path for \"%@\"", assetsName); + if (assetsURL == nil) { + NSLog(@"Failed to find assets path for \"%@\"", bundle); } else { - settings.assets_path = assetsPath.UTF8String; + settings.assets_path = assetsURL.path.UTF8String; // Check if there is an application kernel snapshot in the assets directory we could // potentially use. Looking for the snapshot makes sense only if we have a VM that can use // it. if (!flutter::DartVM::IsRunningPrecompiledCode()) { NSURL* applicationKernelSnapshotURL = - [NSURL URLWithString:@(kApplicationKernelSnapshotFileName) - relativeToURL:[NSURL fileURLWithPath:assetsPath]]; - if ([[NSFileManager defaultManager] fileExistsAtPath:applicationKernelSnapshotURL.path]) { + [assetsURL URLByAppendingPathComponent:@(kApplicationKernelSnapshotFileName)]; + NSError* error; + if ([applicationKernelSnapshotURL checkResourceIsReachableAndReturnError:&error]) { settings.application_kernel_asset = applicationKernelSnapshotURL.path.UTF8String; } else { - NSLog(@"Failed to find snapshot: %@", applicationKernelSnapshotURL.path); + NSLog(@"Failed to find snapshot at %@: %@", applicationKernelSnapshotURL.path, error); } } } @@ -331,11 +356,9 @@ - (instancetype)initWithSettings:(const flutter::Settings&)settings { + (NSString*)flutterAssetsName:(NSBundle*)bundle { if (bundle == nil) { - bundle = FLTFrameworkBundleWithIdentifier([FlutterDartProject defaultBundleIdentifier]); - } - if (bundle == nil) { - bundle = [NSBundle mainBundle]; + bundle = FLTFrameworkBundle(); } + NSString* flutterAssetsName = [bundle objectForInfoDictionaryKey:@"FLTAssetsPath"]; if (flutterAssetsName == nil) { flutterAssetsName = @"Frameworks/App.framework/flutter_assets"; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index a124d2b0296a4..cc6e8bbf0d8a5 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -850,7 +850,11 @@ - (BOOL)createShell:(NSString*)entrypoint ); _isGpuDisabled = +#if APPLICATION_EXTENSION_API_ONLY + NO; +#else [UIApplication sharedApplication].applicationState == UIApplicationStateBackground; +#endif // Create the shell. This is a blocking operation. std::unique_ptr shell = flutter::Shell::Create( /*platform_data=*/platformData, @@ -1452,7 +1456,7 @@ - (void)addMethodCallDelegate:(NSObject*)delegate }]; } -- (void)addApplicationDelegate:(NSObject*)delegate { +- (void)addApplicationDelegate:(NSObject*)delegate NS_EXTENSION_UNAVAILABLE_IOS("") { id appDelegate = [[UIApplication sharedApplication] delegate]; if ([appDelegate conformsToProtocol:@protocol(FlutterAppLifeCycleProvider)]) { id lifeCycleProvider = diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm index 3e81a39f2c779..4697ab344730a 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm @@ -158,6 +158,7 @@ - (void)setSystemChromeApplicationSwitcherDescription:(NSDictionary*)object { } - (void)setSystemChromeEnabledSystemUIOverlays:(NSArray*)overlays { +#if !APPLICATION_EXTENSION_API_ONLY // Checks if the top status bar should be visible. This platform ignores all // other overlays @@ -175,9 +176,11 @@ - (void)setSystemChromeEnabledSystemUIOverlays:(NSArray*)overlays { postNotificationName:FlutterViewControllerHideHomeIndicator object:nil]; } +#endif } - (void)setSystemChromeEnabledSystemUIMode:(NSString*)mode { +#if !APPLICATION_EXTENSION_API_ONLY // Checks if the top status bar should be visible, reflected by edge to edge setting. This // platform ignores all other system ui modes. @@ -195,6 +198,7 @@ - (void)setSystemChromeEnabledSystemUIMode:(NSString*)mode { postNotificationName:FlutterViewControllerHideHomeIndicator object:nil]; } +#endif } - (void)restoreSystemChromeSystemUIOverlays { @@ -231,9 +235,11 @@ - (void)setSystemChromeSystemUIOverlayStyle:(NSDictionary*)message { object:nil userInfo:@{@(kOverlayStyleUpdateNotificationKey) : @(statusBarStyle)}]; } else { +#if !APPLICATION_EXTENSION_API_ONLY // Note: -[UIApplication setStatusBarStyle] is deprecated in iOS9 // in favor of delegating to the view controller [[UIApplication sharedApplication] setStatusBarStyle:statusBarStyle]; +#endif } } @@ -249,11 +255,15 @@ - (void)popSystemNavigator:(BOOL)isAnimated { if (navigationController) { [navigationController popViewControllerAnimated:isAnimated]; } else { +#if !APPLICATION_EXTENSION_API_ONLY UIViewController* rootViewController = [UIApplication sharedApplication].keyWindow.rootViewController; if (engineViewController != rootViewController) { +#endif [engineViewController dismissViewControllerAnimated:isAnimated completion:nil]; +#if !APPLICATION_EXTENSION_API_ONLY } +#endif } } diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 1831b4d2f546b..69c1e23883629 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -813,9 +813,14 @@ - (void)viewDidAppear:(BOOL)animated { if ([_engine.get() viewController] == self) { [self onUserSettingsChanged:nil]; [self onAccessibilityStatusChanged:nil]; + +#if !APPLICATION_EXTENSION_API_ONLY if (UIApplication.sharedApplication.applicationState == UIApplicationStateActive) { +#endif [[_engine.get() lifecycleChannel] sendMessage:@"AppLifecycleState.resumed"]; +#if !APPLICATION_EXTENSION_API_ONLY } +#endif } [super viewDidAppear:animated]; } @@ -1287,8 +1292,13 @@ - (void)viewDidLayoutSubviews { // There is no guarantee that UIKit will layout subviews when the application is active. Creating // the surface when inactive will cause GPU accesses from the background. Only wait for the first // frame to render when the application is actually active. - bool applicationIsActive = + BOOL applicationIsActive = + +#if APPLICATION_EXTENSION_API_ONLY + YES; +#else [UIApplication sharedApplication].applicationState == UIApplicationStateActive; +#endif // This must run after updateViewportMetrics so that the surface creation tasks are queued after // the viewport metrics update tasks. @@ -1808,6 +1818,10 @@ - (void)performOrientationUpdate:(UIInterfaceOrientationMask)new_preferences { _orientationPreferences = new_preferences; if (@available(iOS 16.0, *)) { +#if APPLICATION_EXTENSION_API_ONLY + UIWindowScene* windowScene = self.viewIfLoaded.window.windowScene; + [self performOrientationUpdateOnWindowScene:windowScene]; +#else for (UIScene* scene in UIApplication.sharedApplication.connectedScenes) { if (![scene isKindOfClass:[UIWindowScene class]]) { continue; @@ -1825,7 +1839,9 @@ - (void)performOrientationUpdate:(UIInterfaceOrientationMask)new_preferences { }]; [self setNeedsUpdateOfSupportedInterfaceOrientations]; } +#endif } else { +#if !APPLICATION_EXTENSION_API_ONLY UIInterfaceOrientationMask currentInterfaceOrientation = 1 << [[UIApplication sharedApplication] statusBarOrientation]; if (!(_orientationPreferences & currentInterfaceOrientation)) { @@ -1848,10 +1864,28 @@ - (void)performOrientationUpdate:(UIInterfaceOrientationMask)new_preferences { forKey:@"orientation"]; } } +#endif } } } +- (void)performOrientationUpdateOnWindowScene:(UIWindowScene*)windowScene API_AVAILABLE(ios(16.0)) { + if (windowScene == nil) { + return; + } + + UIWindowSceneGeometryPreferencesIOS* preference = [[UIWindowSceneGeometryPreferencesIOS alloc] + initWithInterfaceOrientations:_orientationPreferences]; + [windowScene + requestGeometryUpdateWithPreferences:preference + errorHandler:^(NSError* error) { + os_log_error(OS_LOG_DEFAULT, + "Failed to change device orientation: %@", error); + }]; + [self setNeedsUpdateOfSupportedInterfaceOrientations]; + [preference release]; +} + - (void)onHideHomeIndicatorNotification:(NSNotification*)notification { self.isHomeIndicatorHidden = YES; } @@ -1951,7 +1985,12 @@ - (void)onUserSettingsChanged:(NSNotification*)notification { } - (CGFloat)textScaleFactor { +#if APPLICATION_EXTENSION_API_ONLY + UIContentSizeCategory category = + self.mainScreenIfViewLoaded.traitCollection.preferredContentSizeCategory; +#else UIContentSizeCategory category = [UIApplication sharedApplication].preferredContentSizeCategory; +#endif // The delta is computed by approximating Apple's typography guidelines: // https://developer.apple.com/ios/human-interface-guidelines/visual-design/typography/ // diff --git a/shell/platform/darwin/macos/BUILD.gn b/shell/platform/darwin/macos/BUILD.gn index da8152cd86497..bc51f8410b56d 100644 --- a/shell/platform/darwin/macos/BUILD.gn +++ b/shell/platform/darwin/macos/BUILD.gn @@ -52,6 +52,9 @@ _flutter_framework_headers_copy_dir = source_set("flutter_framework_source") { visibility = [ ":*" ] + cflags_objcc = flutter_cflags_objcc_arc + cflags_objcc += [ "-fapplication-extension" ] + sources = [ "framework/Source/AccessibilityBridgeMac.h", "framework/Source/AccessibilityBridgeMac.mm", @@ -131,8 +134,9 @@ source_set("flutter_framework_source") { "FLUTTER_FRAMEWORK", "FLUTTER_ENGINE_NO_PROTOTYPES", ] - - cflags_objcc = flutter_cflags_objcc_arc + if (darwin_extension_safe) { + defines += [ "APPLICATION_EXTENSION_API_ONLY=1" ] + } frameworks = [ "Carbon.framework", @@ -153,6 +157,10 @@ shared_library("flutter_framework_dylib") { "-fapplication-extension", ] + if (darwin_extension_safe) { + ldflags += [ "-fapplication-extension" ] + } + deps = [ ":flutter_framework_source" ] } @@ -351,6 +359,5 @@ shared_library("_generate_symlinks_and_verify_framework_module") { frameworks = [ "FlutterMacOS.framework" ] sources = [ "framework/Source/FlutterUmbrellaImportTests.m" ] - deps = [ ":_generate_symlinks" ] } diff --git a/shell/platform/darwin/macos/framework/Source/FlutterUmbrellaImport.m b/shell/platform/darwin/macos/framework/Source/FlutterUmbrellaImport.m new file mode 100644 index 0000000000000..b81280d464c19 --- /dev/null +++ b/shell/platform/darwin/macos/framework/Source/FlutterUmbrellaImport.m @@ -0,0 +1,11 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// FLUTTER_NOLINT: https://github.com/flutter/flutter/issues/93360 + +// The only point of this file is to ensure that the Flutter framework umbrella header can be +// cleanly imported from an Objective-C translation unit. The target that uses this file copies the +// headers to a path that simulates how users would actually import the framework outside of the +// engine source root. +#import diff --git a/tools/gn b/tools/gn index 1b2b52fc9e5c4..efba472afd78f 100755 --- a/tools/gn +++ b/tools/gn @@ -629,6 +629,9 @@ def to_gn_args(args): gn_args['angle_vulkan_tools_dir' ] = '//third_party/vulkan-deps/vulkan-tools/src' + if args.darwin_extension_safe: + gn_args['darwin_extension_safe'] = True + return gn_args @@ -1113,6 +1116,14 @@ def parse_args(args): 'format in the build directory.' ) + parser.add_argument( + '--darwin-extension-safe', + default=False, + action='store_true', + help='Whether the produced Flutter.framework and FlutterMacOS.framework ' + 'is app extension safe. Only for iOS and macOS.' + ) + # Verbose output. parser.add_argument('--verbose', default=False, action='store_true')