Skip to content

Commit

Permalink
[macOS] Migrate @NSApplicationMain attribute to @main
Browse files Browse the repository at this point in the history
  • Loading branch information
loic-sharma committed Apr 16, 2024
1 parent 500ed0b commit a508d3e
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 1 deletion.
2 changes: 2 additions & 0 deletions packages/flutter_tools/lib/src/macos/build_macos.dart
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
@@ -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
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
@@ -1,7 +1,7 @@
import Cocoa
import FlutterMacOS

@NSApplicationMain
@main
class AppDelegate: FlutterAppDelegate {
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
Expand Down
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 a508d3e

Please sign in to comment.