[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 UIKit
|
||||
|
||||
@UIApplicationMain
|
||||
@main
|
||||
@objc class AppDelegate: FlutterAppDelegate {
|
||||
override func application(
|
||||
_ application: UIApplication,
|
||||
|
@ -5,7 +5,7 @@
|
||||
import Flutter
|
||||
import UIKit
|
||||
|
||||
@UIApplicationMain
|
||||
@main
|
||||
@objc class AppDelegate: FlutterAppDelegate {
|
||||
override func application(
|
||||
_ application: UIApplication,
|
||||
|
@ -5,7 +5,7 @@
|
||||
import Flutter
|
||||
import UIKit
|
||||
|
||||
@UIApplicationMain
|
||||
@main
|
||||
@objc class AppDelegate: FlutterAppDelegate {
|
||||
override func application(
|
||||
_ application: UIApplication,
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
@UIApplicationMain
|
||||
@main
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
|
||||
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
|
||||
|
@ -5,7 +5,7 @@
|
||||
import Flutter
|
||||
import UIKit
|
||||
|
||||
@UIApplicationMain
|
||||
@main
|
||||
@objc class AppDelegate: FlutterAppDelegate {
|
||||
override func application(
|
||||
_ application: UIApplication,
|
||||
|
@ -5,7 +5,7 @@
|
||||
import Flutter
|
||||
import UIKit
|
||||
|
||||
@UIApplicationMain
|
||||
@main
|
||||
@objc class AppDelegate: FlutterAppDelegate {
|
||||
override func application(
|
||||
_ application: UIApplication,
|
||||
|
@ -5,7 +5,7 @@
|
||||
import UIKit
|
||||
import Flutter
|
||||
|
||||
@UIApplicationMain
|
||||
@main
|
||||
@objc class AppDelegate: FlutterAppDelegate {
|
||||
override func application(
|
||||
_ application: UIApplication,
|
||||
|
@ -19,7 +19,7 @@ enum MyFlutterErrorCode {
|
||||
static let unavailable = "UNAVAILABLE"
|
||||
}
|
||||
|
||||
@UIApplicationMain
|
||||
@main
|
||||
@objc class AppDelegate: FlutterAppDelegate, FlutterStreamHandler {
|
||||
private var eventSink: FlutterEventSink?
|
||||
|
||||
|
@ -35,6 +35,7 @@ import 'migrations/project_base_configuration_migration.dart';
|
||||
import 'migrations/project_build_location_migration.dart';
|
||||
import 'migrations/remove_bitcode_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 'xcode_build_settings.dart';
|
||||
import 'xcodeproj.dart';
|
||||
@ -158,6 +159,7 @@ Future<XcodeBuildResult> buildXcodeProject({
|
||||
XcodeScriptBuildPhaseMigration(app.project, globals.logger),
|
||||
RemoveBitcodeMigration(app.project, globals.logger),
|
||||
XcodeThinBinaryBuildPhaseInputPathsMigration(app.project, globals.logger),
|
||||
UIApplicationMainDeprecationMigration(app.project, globals.logger),
|
||||
];
|
||||
|
||||
final ProjectMigration migration = ProjectMigration(migrators);
|
||||
|
@ -16,14 +16,12 @@ class RemoveBitcodeMigration extends ProjectMigrator {
|
||||
final File _xcodeProjectInfoFile;
|
||||
|
||||
@override
|
||||
Future<bool> migrate() async {
|
||||
Future<void> migrate() async {
|
||||
if (_xcodeProjectInfoFile.existsSync()) {
|
||||
processFileLines(_xcodeProjectInfoFile);
|
||||
} else {
|
||||
logger.printTrace('Xcode project not found, skipping removing bitcode migration.');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@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');
|
||||
|
||||
/// 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');
|
||||
|
||||
Directory get symlinks => _flutterLibRoot.childDirectory('.symlinks');
|
||||
|
||||
/// True, if the app project is using swift.
|
||||
bool get isSwift {
|
||||
final File appDelegateSwift = _editableDirectory.childDirectory('Runner').childFile('AppDelegate.swift');
|
||||
return appDelegateSwift.existsSync();
|
||||
}
|
||||
/// True if the app project uses Swift.
|
||||
bool get isSwift => appDelegateSwift.existsSync();
|
||||
|
||||
/// Do all plugins support arm64 simulators to run natively on an ARM Mac?
|
||||
Future<bool> pluginsSupportArmSimulator() async {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import Flutter
|
||||
import UIKit
|
||||
|
||||
@UIApplicationMain
|
||||
@main
|
||||
@objc class AppDelegate: FlutterAppDelegate {
|
||||
override func application(
|
||||
_ 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/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/uiapplicationmain_deprecation_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/migrations/cocoapods_script_symlink.dart';
|
||||
@ -906,7 +907,7 @@ platform :ios, '12.0'
|
||||
project,
|
||||
testLogger,
|
||||
);
|
||||
expect(await migration.migrate(), isTrue);
|
||||
await migration.migrate();
|
||||
expect(xcodeProjectInfoFile.existsSync(), isFalse);
|
||||
|
||||
expect(testLogger.traceText, contains('Xcode project not found, skipping removing bitcode migration'));
|
||||
@ -922,7 +923,7 @@ platform :ios, '12.0'
|
||||
project,
|
||||
testLogger,
|
||||
);
|
||||
expect(await migration.migrate(), isTrue);
|
||||
await migration.migrate();
|
||||
|
||||
expect(xcodeProjectInfoFile.lastModifiedSync(), projectLastModified);
|
||||
expect(xcodeProjectInfoFile.readAsStringSync(), xcodeProjectInfoFileContents);
|
||||
@ -943,7 +944,7 @@ platform :ios, '12.0'
|
||||
project,
|
||||
testLogger,
|
||||
);
|
||||
expect(await migration.migrate(), isTrue);
|
||||
await migration.migrate();
|
||||
|
||||
expect(xcodeProjectInfoFile.readAsStringSync(), '''
|
||||
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.'));
|
||||
});
|
||||
});
|
||||
|
||||
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 {
|
||||
@ -1439,6 +1538,9 @@ class FakeIosProject extends Fake implements IosProject {
|
||||
|
||||
@override
|
||||
Directory podRunnerTargetSupportFiles = MemoryFileSystem.test().directory('Pods-Runner');
|
||||
|
||||
@override
|
||||
File appDelegateSwift = MemoryFileSystem.test().file('AppDelegate.swift');
|
||||
}
|
||||
|
||||
class FakeIOSMigrator extends ProjectMigrator {
|
||||
|
Loading…
x
Reference in New Issue
Block a user