mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
[macOS] Migrate @NSApplicationMain attribute to @main (#146848)
This migrates Flutter to use the `@main` attribute introduced in Swift 5.3. The `@NSApplicationMain` 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.a508d3e503
- This updates the macOS app template and adds a migration to replace `@NSApplicationMain` uses with `@main`. 2.f43482786e
- I ran `flutter run -d macos` on each Flutter macOS app in this repository to verify the app migrates and launches successfully. Follow-up to https://github.com/flutter/flutter/pull/146707 Fixes https://github.com/flutter/flutter/issues/143044
This commit is contained in:
parent
e57e456652
commit
86135b7774
@ -5,7 +5,7 @@
|
||||
import Cocoa
|
||||
import FlutterMacOS
|
||||
|
||||
@NSApplicationMain
|
||||
@main
|
||||
class AppDelegate: FlutterAppDelegate {
|
||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||
return true
|
||||
|
@ -5,7 +5,7 @@
|
||||
import Cocoa
|
||||
import FlutterMacOS
|
||||
|
||||
@NSApplicationMain
|
||||
@main
|
||||
class AppDelegate: FlutterAppDelegate {
|
||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||
return true
|
||||
|
@ -5,7 +5,7 @@
|
||||
import Cocoa
|
||||
import FlutterMacOS
|
||||
|
||||
@NSApplicationMain
|
||||
@main
|
||||
class AppDelegate: FlutterAppDelegate {
|
||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||
return true
|
||||
|
@ -5,7 +5,7 @@
|
||||
import Cocoa
|
||||
import FlutterMacOS
|
||||
|
||||
@NSApplicationMain
|
||||
@main
|
||||
class AppDelegate: FlutterAppDelegate {
|
||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||
return true
|
||||
|
@ -5,7 +5,7 @@
|
||||
import Cocoa
|
||||
import FlutterMacOS
|
||||
|
||||
@NSApplicationMain
|
||||
@main
|
||||
class AppDelegate: FlutterAppDelegate {
|
||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||
return true
|
||||
|
@ -5,7 +5,7 @@
|
||||
import Cocoa
|
||||
import FlutterMacOS
|
||||
|
||||
@NSApplicationMain
|
||||
@main
|
||||
class AppDelegate: FlutterAppDelegate {
|
||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||
return true
|
||||
|
@ -5,7 +5,7 @@
|
||||
import Cocoa
|
||||
import FlutterMacOS
|
||||
|
||||
@NSApplicationMain
|
||||
@main
|
||||
class AppDelegate: FlutterAppDelegate {
|
||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||
return true
|
||||
|
@ -5,7 +5,7 @@
|
||||
import Cocoa
|
||||
import FlutterMacOS
|
||||
|
||||
@NSApplicationMain
|
||||
@main
|
||||
class AppDelegate: FlutterAppDelegate {
|
||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||
return true
|
||||
|
@ -5,7 +5,7 @@
|
||||
import Cocoa
|
||||
import FlutterMacOS
|
||||
|
||||
@NSApplicationMain
|
||||
@main
|
||||
class AppDelegate: FlutterAppDelegate {
|
||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||
return true
|
||||
|
@ -5,7 +5,7 @@
|
||||
import Cocoa
|
||||
import FlutterMacOS
|
||||
|
||||
@NSApplicationMain
|
||||
@main
|
||||
class AppDelegate: FlutterAppDelegate {
|
||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||
return true
|
||||
|
@ -5,7 +5,7 @@
|
||||
import Cocoa
|
||||
import FlutterMacOS
|
||||
|
||||
@NSApplicationMain
|
||||
@main
|
||||
class AppDelegate: FlutterAppDelegate {
|
||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||
return true
|
||||
|
@ -5,7 +5,7 @@
|
||||
import Cocoa
|
||||
import FlutterMacOS
|
||||
|
||||
@NSApplicationMain
|
||||
@main
|
||||
class AppDelegate: FlutterAppDelegate {
|
||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||
return true
|
||||
|
@ -5,7 +5,7 @@
|
||||
import Cocoa
|
||||
import FlutterMacOS
|
||||
|
||||
@NSApplicationMain
|
||||
@main
|
||||
class AppDelegate: FlutterAppDelegate {
|
||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||
return true
|
||||
|
@ -5,7 +5,7 @@
|
||||
import Cocoa
|
||||
import FlutterMacOS
|
||||
|
||||
@NSApplicationMain
|
||||
@main
|
||||
class AppDelegate: FlutterAppDelegate {
|
||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||
return true
|
||||
|
@ -5,7 +5,7 @@
|
||||
import Cocoa
|
||||
import FlutterMacOS
|
||||
|
||||
@NSApplicationMain
|
||||
@main
|
||||
class AppDelegate: FlutterAppDelegate {
|
||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||
return true
|
||||
|
@ -24,6 +24,7 @@ import 'application_package.dart';
|
||||
import 'cocoapod_utils.dart';
|
||||
import 'migrations/flutter_application_migration.dart';
|
||||
import 'migrations/macos_deployment_target_migration.dart';
|
||||
import 'migrations/nsapplicationmain_deprecation_migration.dart';
|
||||
import 'migrations/remove_macos_framework_link_and_embedding_migration.dart';
|
||||
|
||||
/// When run in -quiet mode, Xcode should only print from the underlying tasks to stdout.
|
||||
@ -83,6 +84,7 @@ Future<void> buildMacOS({
|
||||
XcodeScriptBuildPhaseMigration(flutterProject.macos, globals.logger),
|
||||
XcodeThinBinaryBuildPhaseInputPathsMigration(flutterProject.macos, globals.logger),
|
||||
FlutterApplicationMigration(flutterProject.macos, globals.logger),
|
||||
NSApplicationMainDeprecationMigration(flutterProject.macos, globals.logger),
|
||||
];
|
||||
|
||||
final ProjectMigration migration = ProjectMigration(migrators);
|
||||
|
@ -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'''
|
||||
@NSApplicationMain
|
||||
class AppDelegate''';
|
||||
|
||||
const String _appDelegateFileAfter = r'''
|
||||
@main
|
||||
class AppDelegate''';
|
||||
|
||||
/// Replace the deprecated `@NSApplicationMain` attribute with `@main`.
|
||||
///
|
||||
/// See:
|
||||
/// https://github.com/apple/swift-evolution/blob/main/proposals/0383-deprecate-uiapplicationmain-and-nsapplicationmain.md
|
||||
class NSApplicationMainDeprecationMigration extends ProjectMigrator {
|
||||
NSApplicationMainDeprecationMigration(
|
||||
MacOSProject 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(
|
||||
'macos/Runner/AppDelegate.swift not found, skipping @main migration.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Migrate the macos/Runner/AppDelegate.swift file.
|
||||
final String original = _appDelegateSwift.readAsStringSync();
|
||||
final String migrated = original.replaceFirst(_appDelegateFileBefore, _appDelegateFileAfter);
|
||||
if (original == migrated) {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.printWarning(
|
||||
'macos/Runner/AppDelegate.swift uses the deprecated @NSApplicationMain attribute, updating.',
|
||||
);
|
||||
_appDelegateSwift.writeAsStringSync(migrated);
|
||||
}
|
||||
}
|
@ -715,6 +715,9 @@ class MacOSProject extends XcodeBasedProject {
|
||||
|
||||
File get pluginRegistrantImplementation => managedDirectory.childFile('GeneratedPluginRegistrant.swift');
|
||||
|
||||
/// The 'AppDelegate.swift' file of the host app. This file might not exist if the app project uses Objective-C.
|
||||
File get appDelegateSwift => hostAppRoot.childDirectory('Runner').childFile('AppDelegate.swift');
|
||||
|
||||
@override
|
||||
File xcodeConfigFor(String mode) => managedDirectory.childFile('Flutter-$mode.xcconfig');
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import Cocoa
|
||||
import FlutterMacOS
|
||||
|
||||
@NSApplicationMain
|
||||
@main
|
||||
class AppDelegate: FlutterAppDelegate {
|
||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||
return true
|
||||
|
@ -8,6 +8,7 @@ import 'package:flutter_tools/src/base/logger.dart';
|
||||
import 'package:flutter_tools/src/ios/plist_parser.dart';
|
||||
import 'package:flutter_tools/src/macos/migrations/flutter_application_migration.dart';
|
||||
import 'package:flutter_tools/src/macos/migrations/macos_deployment_target_migration.dart';
|
||||
import 'package:flutter_tools/src/macos/migrations/nsapplicationmain_deprecation_migration.dart';
|
||||
import 'package:flutter_tools/src/macos/migrations/remove_macos_framework_link_and_embedding_migration.dart';
|
||||
import 'package:flutter_tools/src/project.dart';
|
||||
import 'package:flutter_tools/src/reporting/reporting.dart';
|
||||
@ -400,6 +401,92 @@ platform :osx, '10.14'
|
||||
expect(testLogger.traceText, isEmpty);
|
||||
});
|
||||
});
|
||||
|
||||
group('migrate @NSApplicationMain attribute to @main', () {
|
||||
late MemoryFileSystem memoryFileSystem;
|
||||
late BufferLogger testLogger;
|
||||
late FakeMacOSProject project;
|
||||
late File appDelegateFile;
|
||||
|
||||
setUp(() {
|
||||
memoryFileSystem = MemoryFileSystem();
|
||||
testLogger = BufferLogger.test();
|
||||
project = FakeMacOSProject();
|
||||
appDelegateFile = memoryFileSystem.file('AppDelegate.swift');
|
||||
project.appDelegateSwift = appDelegateFile;
|
||||
});
|
||||
|
||||
testWithoutContext('skipped if files are missing', () async {
|
||||
final NSApplicationMainDeprecationMigration migration = NSApplicationMainDeprecationMigration(
|
||||
project,
|
||||
testLogger,
|
||||
);
|
||||
await migration.migrate();
|
||||
expect(appDelegateFile.existsSync(), isFalse);
|
||||
|
||||
expect(testLogger.statusText, isEmpty);
|
||||
});
|
||||
|
||||
testWithoutContext('skipped if nothing to upgrade', () async {
|
||||
const String appDelegateContents = '''
|
||||
import Cocoa
|
||||
import FlutterMacOS
|
||||
|
||||
@main
|
||||
class AppDelegate: FlutterAppDelegate {
|
||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
''';
|
||||
appDelegateFile.writeAsStringSync(appDelegateContents);
|
||||
final DateTime lastModified = appDelegateFile.lastModifiedSync();
|
||||
|
||||
final NSApplicationMainDeprecationMigration migration = NSApplicationMainDeprecationMigration(
|
||||
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 Cocoa
|
||||
import FlutterMacOS
|
||||
|
||||
@NSApplicationMain
|
||||
class AppDelegate: FlutterAppDelegate {
|
||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
''');
|
||||
|
||||
final NSApplicationMainDeprecationMigration migration = NSApplicationMainDeprecationMigration(
|
||||
project,
|
||||
testLogger,
|
||||
);
|
||||
await migration.migrate();
|
||||
|
||||
expect(appDelegateFile.readAsStringSync(), '''
|
||||
import Cocoa
|
||||
import FlutterMacOS
|
||||
|
||||
@main
|
||||
class AppDelegate: FlutterAppDelegate {
|
||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
''');
|
||||
expect(testLogger.warningText, contains('uses the deprecated @NSApplicationMain attribute, updating'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class FakeMacOSProject extends Fake implements MacOSProject {
|
||||
@ -411,4 +498,7 @@ class FakeMacOSProject extends Fake implements MacOSProject {
|
||||
|
||||
@override
|
||||
File podfile = MemoryFileSystem.test().file('Podfile');
|
||||
|
||||
@override
|
||||
File appDelegateSwift = MemoryFileSystem.test().file('AppDelegate.swift');
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user