[iOS] Migrate @UIApplicationMain attribute to @main (#146707)
This migrates Flutter to use the `@main` attribute introduced in Swift 5.3. The `@UIApplicationMain` attribute is deprecated and will be removed in Swift 6. See: https://github.com/apple/swift-evolution/blob/main/proposals/0383-deprecate-uiapplicationmain-and-nsapplicationmain.md This change is split into two commits: 1.ad18797428
- This updates the iOS app template and adds a migration to replace `@UIApplicationMain` uses with `@main`. 2.8ecbb2f29f
- I ran `flutter run` on each Flutter iOS app in this repository to verify the app migrates and launches successfully. Part of https://github.com/flutter/flutter/issues/143044
This commit is contained in:
parent
5a0369df16
commit
194fefaa53
@ -5,7 +5,7 @@
|
|||||||
import Flutter
|
import Flutter
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
@UIApplicationMain
|
@main
|
||||||
@objc class AppDelegate: FlutterAppDelegate {
|
@objc class AppDelegate: FlutterAppDelegate {
|
||||||
override func application(
|
override func application(
|
||||||
_ application: UIApplication,
|
_ application: UIApplication,
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
import Flutter
|
import Flutter
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
@UIApplicationMain
|
@main
|
||||||
@objc class AppDelegate: FlutterAppDelegate {
|
@objc class AppDelegate: FlutterAppDelegate {
|
||||||
override func application(
|
override func application(
|
||||||
_ application: UIApplication,
|
_ application: UIApplication,
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
import Flutter
|
import Flutter
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
@UIApplicationMain
|
@main
|
||||||
@objc class AppDelegate: FlutterAppDelegate {
|
@objc class AppDelegate: FlutterAppDelegate {
|
||||||
override func application(
|
override func application(
|
||||||
_ application: UIApplication,
|
_ application: UIApplication,
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
@UIApplicationMain
|
@main
|
||||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
|
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
|
||||||
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
|
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
import Flutter
|
import Flutter
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
@UIApplicationMain
|
@main
|
||||||
@objc class AppDelegate: FlutterAppDelegate {
|
@objc class AppDelegate: FlutterAppDelegate {
|
||||||
override func application(
|
override func application(
|
||||||
_ application: UIApplication,
|
_ application: UIApplication,
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
import Flutter
|
import Flutter
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
@UIApplicationMain
|
@main
|
||||||
@objc class AppDelegate: FlutterAppDelegate {
|
@objc class AppDelegate: FlutterAppDelegate {
|
||||||
override func application(
|
override func application(
|
||||||
_ application: UIApplication,
|
_ application: UIApplication,
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
import UIKit
|
import UIKit
|
||||||
import Flutter
|
import Flutter
|
||||||
|
|
||||||
@UIApplicationMain
|
@main
|
||||||
@objc class AppDelegate: FlutterAppDelegate {
|
@objc class AppDelegate: FlutterAppDelegate {
|
||||||
override func application(
|
override func application(
|
||||||
_ application: UIApplication,
|
_ application: UIApplication,
|
||||||
|
@ -19,7 +19,7 @@ enum MyFlutterErrorCode {
|
|||||||
static let unavailable = "UNAVAILABLE"
|
static let unavailable = "UNAVAILABLE"
|
||||||
}
|
}
|
||||||
|
|
||||||
@UIApplicationMain
|
@main
|
||||||
@objc class AppDelegate: FlutterAppDelegate, FlutterStreamHandler {
|
@objc class AppDelegate: FlutterAppDelegate, FlutterStreamHandler {
|
||||||
private var eventSink: FlutterEventSink?
|
private var eventSink: FlutterEventSink?
|
||||||
|
|
||||||
|
@ -35,6 +35,7 @@ import 'migrations/project_base_configuration_migration.dart';
|
|||||||
import 'migrations/project_build_location_migration.dart';
|
import 'migrations/project_build_location_migration.dart';
|
||||||
import 'migrations/remove_bitcode_migration.dart';
|
import 'migrations/remove_bitcode_migration.dart';
|
||||||
import 'migrations/remove_framework_link_and_embedding_migration.dart';
|
import 'migrations/remove_framework_link_and_embedding_migration.dart';
|
||||||
|
import 'migrations/uiapplicationmain_deprecation_migration.dart';
|
||||||
import 'migrations/xcode_build_system_migration.dart';
|
import 'migrations/xcode_build_system_migration.dart';
|
||||||
import 'xcode_build_settings.dart';
|
import 'xcode_build_settings.dart';
|
||||||
import 'xcodeproj.dart';
|
import 'xcodeproj.dart';
|
||||||
@ -158,6 +159,7 @@ Future<XcodeBuildResult> buildXcodeProject({
|
|||||||
XcodeScriptBuildPhaseMigration(app.project, globals.logger),
|
XcodeScriptBuildPhaseMigration(app.project, globals.logger),
|
||||||
RemoveBitcodeMigration(app.project, globals.logger),
|
RemoveBitcodeMigration(app.project, globals.logger),
|
||||||
XcodeThinBinaryBuildPhaseInputPathsMigration(app.project, globals.logger),
|
XcodeThinBinaryBuildPhaseInputPathsMigration(app.project, globals.logger),
|
||||||
|
UIApplicationMainDeprecationMigration(app.project, globals.logger),
|
||||||
];
|
];
|
||||||
|
|
||||||
final ProjectMigration migration = ProjectMigration(migrators);
|
final ProjectMigration migration = ProjectMigration(migrators);
|
||||||
|
@ -16,14 +16,12 @@ class RemoveBitcodeMigration extends ProjectMigrator {
|
|||||||
final File _xcodeProjectInfoFile;
|
final File _xcodeProjectInfoFile;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> migrate() async {
|
Future<void> migrate() async {
|
||||||
if (_xcodeProjectInfoFile.existsSync()) {
|
if (_xcodeProjectInfoFile.existsSync()) {
|
||||||
processFileLines(_xcodeProjectInfoFile);
|
processFileLines(_xcodeProjectInfoFile);
|
||||||
} else {
|
} else {
|
||||||
logger.printTrace('Xcode project not found, skipping removing bitcode migration.');
|
logger.printTrace('Xcode project not found, skipping removing bitcode migration.');
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -0,0 +1,51 @@
|
|||||||
|
// Copyright 2014 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.
|
||||||
|
|
||||||
|
import '../../base/file_system.dart';
|
||||||
|
import '../../base/project_migrator.dart';
|
||||||
|
import '../../xcode_project.dart';
|
||||||
|
|
||||||
|
const String _appDelegateFileBefore = r'''
|
||||||
|
@UIApplicationMain
|
||||||
|
@objc class AppDelegate''';
|
||||||
|
|
||||||
|
const String _appDelegateFileAfter = r'''
|
||||||
|
@main
|
||||||
|
@objc class AppDelegate''';
|
||||||
|
|
||||||
|
/// Replace the deprecated `@UIApplicationMain` attribute with `@main`.
|
||||||
|
///
|
||||||
|
/// See:
|
||||||
|
/// https://github.com/apple/swift-evolution/blob/main/proposals/0383-deprecate-uiapplicationmain-and-nsapplicationmain.md
|
||||||
|
class UIApplicationMainDeprecationMigration extends ProjectMigrator {
|
||||||
|
UIApplicationMainDeprecationMigration(
|
||||||
|
IosProject project,
|
||||||
|
super.logger,
|
||||||
|
) : _appDelegateSwift = project.appDelegateSwift;
|
||||||
|
|
||||||
|
final File _appDelegateSwift;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> migrate() async {
|
||||||
|
// Skip this migration if the project uses Objective-C.
|
||||||
|
if (!_appDelegateSwift.existsSync()) {
|
||||||
|
logger.printTrace(
|
||||||
|
'ios/Runner/AppDelegate.swift not found, skipping @main migration.',
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Migrate the ios/Runner/AppDelegate.swift file.
|
||||||
|
final String original = _appDelegateSwift.readAsStringSync();
|
||||||
|
final String migrated = original.replaceFirst(_appDelegateFileBefore, _appDelegateFileAfter);
|
||||||
|
if (original == migrated) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.printWarning(
|
||||||
|
'ios/Runner/AppDelegate.swift uses the deprecated @UIApplicationMain attribute, updating.',
|
||||||
|
);
|
||||||
|
_appDelegateSwift.writeAsStringSync(migrated);
|
||||||
|
}
|
||||||
|
}
|
@ -178,15 +178,15 @@ class IosProject extends XcodeBasedProject {
|
|||||||
|
|
||||||
File get appFrameworkInfoPlist => _flutterLibRoot.childDirectory('Flutter').childFile('AppFrameworkInfo.plist');
|
File get appFrameworkInfoPlist => _flutterLibRoot.childDirectory('Flutter').childFile('AppFrameworkInfo.plist');
|
||||||
|
|
||||||
|
/// The 'AppDelegate.swift' file of the host app. This file might not exist if the app project uses Objective-C.
|
||||||
|
File get appDelegateSwift => _editableDirectory.childDirectory('Runner').childFile('AppDelegate.swift');
|
||||||
|
|
||||||
File get infoPlist => _editableDirectory.childDirectory('Runner').childFile('Info.plist');
|
File get infoPlist => _editableDirectory.childDirectory('Runner').childFile('Info.plist');
|
||||||
|
|
||||||
Directory get symlinks => _flutterLibRoot.childDirectory('.symlinks');
|
Directory get symlinks => _flutterLibRoot.childDirectory('.symlinks');
|
||||||
|
|
||||||
/// True, if the app project is using swift.
|
/// True if the app project uses Swift.
|
||||||
bool get isSwift {
|
bool get isSwift => appDelegateSwift.existsSync();
|
||||||
final File appDelegateSwift = _editableDirectory.childDirectory('Runner').childFile('AppDelegate.swift');
|
|
||||||
return appDelegateSwift.existsSync();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Do all plugins support arm64 simulators to run natively on an ARM Mac?
|
/// Do all plugins support arm64 simulators to run natively on an ARM Mac?
|
||||||
Future<bool> pluginsSupportArmSimulator() async {
|
Future<bool> pluginsSupportArmSimulator() async {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import Flutter
|
import Flutter
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
@UIApplicationMain
|
@main
|
||||||
@objc class AppDelegate: FlutterAppDelegate {
|
@objc class AppDelegate: FlutterAppDelegate {
|
||||||
override func application(
|
override func application(
|
||||||
_ application: UIApplication,
|
_ application: UIApplication,
|
||||||
|
@ -13,6 +13,7 @@ import 'package:flutter_tools/src/ios/migrations/project_base_configuration_migr
|
|||||||
import 'package:flutter_tools/src/ios/migrations/project_build_location_migration.dart';
|
import 'package:flutter_tools/src/ios/migrations/project_build_location_migration.dart';
|
||||||
import 'package:flutter_tools/src/ios/migrations/remove_bitcode_migration.dart';
|
import 'package:flutter_tools/src/ios/migrations/remove_bitcode_migration.dart';
|
||||||
import 'package:flutter_tools/src/ios/migrations/remove_framework_link_and_embedding_migration.dart';
|
import 'package:flutter_tools/src/ios/migrations/remove_framework_link_and_embedding_migration.dart';
|
||||||
|
import 'package:flutter_tools/src/ios/migrations/uiapplicationmain_deprecation_migration.dart';
|
||||||
import 'package:flutter_tools/src/ios/migrations/xcode_build_system_migration.dart';
|
import 'package:flutter_tools/src/ios/migrations/xcode_build_system_migration.dart';
|
||||||
import 'package:flutter_tools/src/ios/xcodeproj.dart';
|
import 'package:flutter_tools/src/ios/xcodeproj.dart';
|
||||||
import 'package:flutter_tools/src/migrations/cocoapods_script_symlink.dart';
|
import 'package:flutter_tools/src/migrations/cocoapods_script_symlink.dart';
|
||||||
@ -906,7 +907,7 @@ platform :ios, '12.0'
|
|||||||
project,
|
project,
|
||||||
testLogger,
|
testLogger,
|
||||||
);
|
);
|
||||||
expect(await migration.migrate(), isTrue);
|
await migration.migrate();
|
||||||
expect(xcodeProjectInfoFile.existsSync(), isFalse);
|
expect(xcodeProjectInfoFile.existsSync(), isFalse);
|
||||||
|
|
||||||
expect(testLogger.traceText, contains('Xcode project not found, skipping removing bitcode migration'));
|
expect(testLogger.traceText, contains('Xcode project not found, skipping removing bitcode migration'));
|
||||||
@ -922,7 +923,7 @@ platform :ios, '12.0'
|
|||||||
project,
|
project,
|
||||||
testLogger,
|
testLogger,
|
||||||
);
|
);
|
||||||
expect(await migration.migrate(), isTrue);
|
await migration.migrate();
|
||||||
|
|
||||||
expect(xcodeProjectInfoFile.lastModifiedSync(), projectLastModified);
|
expect(xcodeProjectInfoFile.lastModifiedSync(), projectLastModified);
|
||||||
expect(xcodeProjectInfoFile.readAsStringSync(), xcodeProjectInfoFileContents);
|
expect(xcodeProjectInfoFile.readAsStringSync(), xcodeProjectInfoFileContents);
|
||||||
@ -943,7 +944,7 @@ platform :ios, '12.0'
|
|||||||
project,
|
project,
|
||||||
testLogger,
|
testLogger,
|
||||||
);
|
);
|
||||||
expect(await migration.migrate(), isTrue);
|
await migration.migrate();
|
||||||
|
|
||||||
expect(xcodeProjectInfoFile.readAsStringSync(), '''
|
expect(xcodeProjectInfoFile.readAsStringSync(), '''
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
@ -1408,6 +1409,104 @@ LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/../Frame
|
|||||||
expect(testLogger.statusText, contains('Adding input path to Thin Binary build phase.'));
|
expect(testLogger.statusText, contains('Adding input path to Thin Binary build phase.'));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group('migrate @UIApplicationMain attribute to @main', () {
|
||||||
|
late MemoryFileSystem memoryFileSystem;
|
||||||
|
late BufferLogger testLogger;
|
||||||
|
late FakeIosProject project;
|
||||||
|
late File appDelegateFile;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
memoryFileSystem = MemoryFileSystem();
|
||||||
|
testLogger = BufferLogger.test();
|
||||||
|
project = FakeIosProject();
|
||||||
|
appDelegateFile = memoryFileSystem.file('AppDelegate.swift');
|
||||||
|
project.appDelegateSwift = appDelegateFile;
|
||||||
|
});
|
||||||
|
|
||||||
|
testWithoutContext('skipped if files are missing', () async {
|
||||||
|
final UIApplicationMainDeprecationMigration migration = UIApplicationMainDeprecationMigration(
|
||||||
|
project,
|
||||||
|
testLogger,
|
||||||
|
);
|
||||||
|
await migration.migrate();
|
||||||
|
expect(appDelegateFile.existsSync(), isFalse);
|
||||||
|
|
||||||
|
expect(testLogger.statusText, isEmpty);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWithoutContext('skipped if nothing to upgrade', () async {
|
||||||
|
const String appDelegateContents = '''
|
||||||
|
import Flutter
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
@main
|
||||||
|
@objc class AppDelegate: FlutterAppDelegate {
|
||||||
|
override func application(
|
||||||
|
_ application: UIApplication,
|
||||||
|
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||||
|
) -> Bool {
|
||||||
|
GeneratedPluginRegistrant.register(with: self)
|
||||||
|
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
appDelegateFile.writeAsStringSync(appDelegateContents);
|
||||||
|
final DateTime lastModified = appDelegateFile.lastModifiedSync();
|
||||||
|
|
||||||
|
final UIApplicationMainDeprecationMigration migration = UIApplicationMainDeprecationMigration(
|
||||||
|
project,
|
||||||
|
testLogger,
|
||||||
|
);
|
||||||
|
await migration.migrate();
|
||||||
|
|
||||||
|
expect(appDelegateFile.lastModifiedSync(), lastModified);
|
||||||
|
expect(appDelegateFile.readAsStringSync(), appDelegateContents);
|
||||||
|
|
||||||
|
expect(testLogger.statusText, isEmpty);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWithoutContext('updates AppDelegate.swift', () async {
|
||||||
|
appDelegateFile.writeAsStringSync('''
|
||||||
|
import Flutter
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
@UIApplicationMain
|
||||||
|
@objc class AppDelegate: FlutterAppDelegate {
|
||||||
|
override func application(
|
||||||
|
_ application: UIApplication,
|
||||||
|
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||||
|
) -> Bool {
|
||||||
|
GeneratedPluginRegistrant.register(with: self)
|
||||||
|
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''');
|
||||||
|
|
||||||
|
final UIApplicationMainDeprecationMigration migration = UIApplicationMainDeprecationMigration(
|
||||||
|
project,
|
||||||
|
testLogger,
|
||||||
|
);
|
||||||
|
await migration.migrate();
|
||||||
|
|
||||||
|
expect(appDelegateFile.readAsStringSync(), '''
|
||||||
|
import Flutter
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
@main
|
||||||
|
@objc class AppDelegate: FlutterAppDelegate {
|
||||||
|
override func application(
|
||||||
|
_ application: UIApplication,
|
||||||
|
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||||
|
) -> Bool {
|
||||||
|
GeneratedPluginRegistrant.register(with: self)
|
||||||
|
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''');
|
||||||
|
expect(testLogger.warningText, contains('uses the deprecated @UIApplicationMain attribute, updating'));
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class FakeIosProject extends Fake implements IosProject {
|
class FakeIosProject extends Fake implements IosProject {
|
||||||
@ -1439,6 +1538,9 @@ class FakeIosProject extends Fake implements IosProject {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Directory podRunnerTargetSupportFiles = MemoryFileSystem.test().directory('Pods-Runner');
|
Directory podRunnerTargetSupportFiles = MemoryFileSystem.test().directory('Pods-Runner');
|
||||||
|
|
||||||
|
@override
|
||||||
|
File appDelegateSwift = MemoryFileSystem.test().file('AppDelegate.swift');
|
||||||
}
|
}
|
||||||
|
|
||||||
class FakeIOSMigrator extends ProjectMigrator {
|
class FakeIOSMigrator extends ProjectMigrator {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user