Skip to content

Commit

Permalink
[macOS] Migrate @NSApplicationMain attribute to @main (flutter#146848)
Browse files Browse the repository at this point in the history
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. flutter@a508d3e - This updates the macOS app template and adds a migration to replace `@NSApplicationMain` uses with `@main`. 
2. flutter@f434827 - 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 flutter#146707
Fixes flutter#143044
  • Loading branch information
loic-sharma authored and gilnobrega committed Apr 22, 2024
1 parent 8faa090 commit ac17873
Show file tree
Hide file tree
Showing 20 changed files with 162 additions and 16 deletions.
2 changes: 1 addition & 1 deletion dev/a11y_assessments/macos/Runner/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import Cocoa
import FlutterMacOS

@NSApplicationMain
@main
class AppDelegate: FlutterAppDelegate {
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import Cocoa
import FlutterMacOS

@NSApplicationMain
@main
class AppDelegate: FlutterAppDelegate {
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import Cocoa
import FlutterMacOS

@NSApplicationMain
@main
class AppDelegate: FlutterAppDelegate {
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import Cocoa
import FlutterMacOS

@NSApplicationMain
@main
class AppDelegate: FlutterAppDelegate {
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import Cocoa
import FlutterMacOS

@NSApplicationMain
@main
class AppDelegate: FlutterAppDelegate {
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import Cocoa
import FlutterMacOS

@NSApplicationMain
@main
class AppDelegate: FlutterAppDelegate {
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
Expand Down
2 changes: 1 addition & 1 deletion dev/integration_tests/ui/macos/Runner/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import Cocoa
import FlutterMacOS

@NSApplicationMain
@main
class AppDelegate: FlutterAppDelegate {
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
Expand Down
2 changes: 1 addition & 1 deletion dev/manual_tests/macos/Runner/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import Cocoa
import FlutterMacOS

@NSApplicationMain
@main
class AppDelegate: FlutterAppDelegate {
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
Expand Down
2 changes: 1 addition & 1 deletion examples/api/macos/Runner/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import Cocoa
import FlutterMacOS

@NSApplicationMain
@main
class AppDelegate: FlutterAppDelegate {
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
Expand Down
2 changes: 1 addition & 1 deletion examples/flutter_view/macos/Runner/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import Cocoa
import FlutterMacOS

@NSApplicationMain
@main
class AppDelegate: FlutterAppDelegate {
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
Expand Down
2 changes: 1 addition & 1 deletion examples/hello_world/macos/Runner/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import Cocoa
import FlutterMacOS

@NSApplicationMain
@main
class AppDelegate: FlutterAppDelegate {
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
Expand Down
2 changes: 1 addition & 1 deletion examples/image_list/macos/Runner/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import Cocoa
import FlutterMacOS

@NSApplicationMain
@main
class AppDelegate: FlutterAppDelegate {
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
Expand Down
2 changes: 1 addition & 1 deletion examples/layers/macos/Runner/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import Cocoa
import FlutterMacOS

@NSApplicationMain
@main
class AppDelegate: FlutterAppDelegate {
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
Expand Down
2 changes: 1 addition & 1 deletion examples/platform_channel/macos/Runner/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import Cocoa
import FlutterMacOS

@NSApplicationMain
@main
class AppDelegate: FlutterAppDelegate {
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
Expand Down
2 changes: 1 addition & 1 deletion examples/platform_view/macos/Runner/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import Cocoa
import FlutterMacOS

@NSApplicationMain
@main
class AppDelegate: FlutterAppDelegate {
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
Expand Down
2 changes: 2 additions & 0 deletions packages/flutter_tools/lib/src/macos/build_macos.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
3 changes: 3 additions & 0 deletions packages/flutter_tools/lib/src/xcode_project.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Cocoa
import FlutterMacOS

@NSApplicationMain
@main
class AppDelegate: FlutterAppDelegate {
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 {
Expand All @@ -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');
}

0 comments on commit ac17873

Please sign in to comment.