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 Cocoa
|
||||||
import FlutterMacOS
|
import FlutterMacOS
|
||||||
|
|
||||||
@NSApplicationMain
|
@main
|
||||||
class AppDelegate: FlutterAppDelegate {
|
class AppDelegate: FlutterAppDelegate {
|
||||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||||
return true
|
return true
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
import Cocoa
|
import Cocoa
|
||||||
import FlutterMacOS
|
import FlutterMacOS
|
||||||
|
|
||||||
@NSApplicationMain
|
@main
|
||||||
class AppDelegate: FlutterAppDelegate {
|
class AppDelegate: FlutterAppDelegate {
|
||||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||||
return true
|
return true
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
import Cocoa
|
import Cocoa
|
||||||
import FlutterMacOS
|
import FlutterMacOS
|
||||||
|
|
||||||
@NSApplicationMain
|
@main
|
||||||
class AppDelegate: FlutterAppDelegate {
|
class AppDelegate: FlutterAppDelegate {
|
||||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||||
return true
|
return true
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
import Cocoa
|
import Cocoa
|
||||||
import FlutterMacOS
|
import FlutterMacOS
|
||||||
|
|
||||||
@NSApplicationMain
|
@main
|
||||||
class AppDelegate: FlutterAppDelegate {
|
class AppDelegate: FlutterAppDelegate {
|
||||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||||
return true
|
return true
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
import Cocoa
|
import Cocoa
|
||||||
import FlutterMacOS
|
import FlutterMacOS
|
||||||
|
|
||||||
@NSApplicationMain
|
@main
|
||||||
class AppDelegate: FlutterAppDelegate {
|
class AppDelegate: FlutterAppDelegate {
|
||||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||||
return true
|
return true
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
import Cocoa
|
import Cocoa
|
||||||
import FlutterMacOS
|
import FlutterMacOS
|
||||||
|
|
||||||
@NSApplicationMain
|
@main
|
||||||
class AppDelegate: FlutterAppDelegate {
|
class AppDelegate: FlutterAppDelegate {
|
||||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||||
return true
|
return true
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
import Cocoa
|
import Cocoa
|
||||||
import FlutterMacOS
|
import FlutterMacOS
|
||||||
|
|
||||||
@NSApplicationMain
|
@main
|
||||||
class AppDelegate: FlutterAppDelegate {
|
class AppDelegate: FlutterAppDelegate {
|
||||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||||
return true
|
return true
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
import Cocoa
|
import Cocoa
|
||||||
import FlutterMacOS
|
import FlutterMacOS
|
||||||
|
|
||||||
@NSApplicationMain
|
@main
|
||||||
class AppDelegate: FlutterAppDelegate {
|
class AppDelegate: FlutterAppDelegate {
|
||||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||||
return true
|
return true
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
import Cocoa
|
import Cocoa
|
||||||
import FlutterMacOS
|
import FlutterMacOS
|
||||||
|
|
||||||
@NSApplicationMain
|
@main
|
||||||
class AppDelegate: FlutterAppDelegate {
|
class AppDelegate: FlutterAppDelegate {
|
||||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||||
return true
|
return true
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
import Cocoa
|
import Cocoa
|
||||||
import FlutterMacOS
|
import FlutterMacOS
|
||||||
|
|
||||||
@NSApplicationMain
|
@main
|
||||||
class AppDelegate: FlutterAppDelegate {
|
class AppDelegate: FlutterAppDelegate {
|
||||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||||
return true
|
return true
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
import Cocoa
|
import Cocoa
|
||||||
import FlutterMacOS
|
import FlutterMacOS
|
||||||
|
|
||||||
@NSApplicationMain
|
@main
|
||||||
class AppDelegate: FlutterAppDelegate {
|
class AppDelegate: FlutterAppDelegate {
|
||||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||||
return true
|
return true
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
import Cocoa
|
import Cocoa
|
||||||
import FlutterMacOS
|
import FlutterMacOS
|
||||||
|
|
||||||
@NSApplicationMain
|
@main
|
||||||
class AppDelegate: FlutterAppDelegate {
|
class AppDelegate: FlutterAppDelegate {
|
||||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||||
return true
|
return true
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
import Cocoa
|
import Cocoa
|
||||||
import FlutterMacOS
|
import FlutterMacOS
|
||||||
|
|
||||||
@NSApplicationMain
|
@main
|
||||||
class AppDelegate: FlutterAppDelegate {
|
class AppDelegate: FlutterAppDelegate {
|
||||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||||
return true
|
return true
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
import Cocoa
|
import Cocoa
|
||||||
import FlutterMacOS
|
import FlutterMacOS
|
||||||
|
|
||||||
@NSApplicationMain
|
@main
|
||||||
class AppDelegate: FlutterAppDelegate {
|
class AppDelegate: FlutterAppDelegate {
|
||||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||||
return true
|
return true
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
import Cocoa
|
import Cocoa
|
||||||
import FlutterMacOS
|
import FlutterMacOS
|
||||||
|
|
||||||
@NSApplicationMain
|
@main
|
||||||
class AppDelegate: FlutterAppDelegate {
|
class AppDelegate: FlutterAppDelegate {
|
||||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||||
return true
|
return true
|
||||||
|
@ -24,6 +24,7 @@ import 'application_package.dart';
|
|||||||
import 'cocoapod_utils.dart';
|
import 'cocoapod_utils.dart';
|
||||||
import 'migrations/flutter_application_migration.dart';
|
import 'migrations/flutter_application_migration.dart';
|
||||||
import 'migrations/macos_deployment_target_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';
|
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.
|
/// 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),
|
XcodeScriptBuildPhaseMigration(flutterProject.macos, globals.logger),
|
||||||
XcodeThinBinaryBuildPhaseInputPathsMigration(flutterProject.macos, globals.logger),
|
XcodeThinBinaryBuildPhaseInputPathsMigration(flutterProject.macos, globals.logger),
|
||||||
FlutterApplicationMigration(flutterProject.macos, globals.logger),
|
FlutterApplicationMigration(flutterProject.macos, globals.logger),
|
||||||
|
NSApplicationMainDeprecationMigration(flutterProject.macos, globals.logger),
|
||||||
];
|
];
|
||||||
|
|
||||||
final ProjectMigration migration = ProjectMigration(migrators);
|
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');
|
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
|
@override
|
||||||
File xcodeConfigFor(String mode) => managedDirectory.childFile('Flutter-$mode.xcconfig');
|
File xcodeConfigFor(String mode) => managedDirectory.childFile('Flutter-$mode.xcconfig');
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import Cocoa
|
import Cocoa
|
||||||
import FlutterMacOS
|
import FlutterMacOS
|
||||||
|
|
||||||
@NSApplicationMain
|
@main
|
||||||
class AppDelegate: FlutterAppDelegate {
|
class AppDelegate: FlutterAppDelegate {
|
||||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||||
return true
|
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/ios/plist_parser.dart';
|
||||||
import 'package:flutter_tools/src/macos/migrations/flutter_application_migration.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/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/macos/migrations/remove_macos_framework_link_and_embedding_migration.dart';
|
||||||
import 'package:flutter_tools/src/project.dart';
|
import 'package:flutter_tools/src/project.dart';
|
||||||
import 'package:flutter_tools/src/reporting/reporting.dart';
|
import 'package:flutter_tools/src/reporting/reporting.dart';
|
||||||
@ -400,6 +401,92 @@ platform :osx, '10.14'
|
|||||||
expect(testLogger.traceText, isEmpty);
|
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 {
|
class FakeMacOSProject extends Fake implements MacOSProject {
|
||||||
@ -411,4 +498,7 @@ class FakeMacOSProject extends Fake implements MacOSProject {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
File podfile = MemoryFileSystem.test().file('Podfile');
|
File podfile = MemoryFileSystem.test().file('Podfile');
|
||||||
|
|
||||||
|
@override
|
||||||
|
File appDelegateSwift = MemoryFileSystem.test().file('AppDelegate.swift');
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user