Skip to content

Commit eaaacdc

Browse files
authored
Allow iOS and macOS plugins to share darwin directory (#115337)
1 parent a02b9d2 commit eaaacdc

File tree

10 files changed

+218
-25
lines changed

10 files changed

+218
-25
lines changed

.ci.yaml

+19
Original file line numberDiff line numberDiff line change
@@ -2944,6 +2944,25 @@ targets:
29442944
- bin/**
29452945
- .ci.yaml
29462946

2947+
- name: Mac plugin_test_macos
2948+
bringup: true
2949+
recipe: devicelab/devicelab_drone
2950+
timeout: 60
2951+
properties:
2952+
dependencies: >-
2953+
[
2954+
{"dependency": "xcode", "version": "14a5294e"},
2955+
{"dependency": "gems", "version": "v3.3.14"}
2956+
]
2957+
tags: >
2958+
["devicelab", "hostonly", "mac"]
2959+
task_name: plugin_test_macos
2960+
runIf:
2961+
- dev/**
2962+
- packages/flutter_tools/**
2963+
- bin/**
2964+
- .ci.yaml
2965+
29472966
- name: Mac_x64 tool_host_cross_arch_tests
29482967
recipe: flutter/flutter_drone
29492968
timeout: 60

TESTOWNERS

+1
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@
248248
/dev/devicelab/bin/tasks/platform_view_win_desktop__start_up.dart @yaakovschectman @flutter/desktop
249249
/dev/devicelab/bin/tasks/plugin_lint_mac.dart @stuartmorgan @flutter/plugin
250250
/dev/devicelab/bin/tasks/plugin_test_ios.dart @jmagman @flutter/ios
251+
/dev/devicelab/bin/tasks/plugin_test_macos.dart @jmagman @flutter/desktop
251252
/dev/devicelab/bin/tasks/plugin_test.dart @stuartmorgan @flutter/plugin
252253
/dev/devicelab/bin/tasks/run_debug_test_android.dart @zanderso @flutter/tool
253254
/dev/devicelab/bin/tasks/run_debug_test_macos.dart @cbracken @flutter/tool

dev/devicelab/bin/tasks/plugin_test_ios.dart

+2-3
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,11 @@ Future<void> main() async {
99
await task(combine(<TaskFunction>[
1010
PluginTest('ios', <String>['-i', 'objc', '--platforms=ios']).call,
1111
PluginTest('ios', <String>['-i', 'swift', '--platforms=ios']).call,
12-
PluginTest('macos', <String>['--platforms=macos']).call,
1312
// Test that Dart-only plugins are supported.
1413
PluginTest('ios', <String>['--platforms=ios'], dartOnlyPlugin: true).call,
15-
PluginTest('macos', <String>['--platforms=macos'], dartOnlyPlugin: true).call,
14+
// Test that shared darwin directories are supported.
15+
PluginTest('ios', <String>['--platforms=ios,macos'], sharedDarwinSource: true).call,
1616
// Test that FFI plugins are supported.
1717
PluginTest('ios', <String>['--platforms=ios'], template: 'plugin_ffi').call,
18-
PluginTest('macos', <String>['--platforms=macos'], template: 'plugin_ffi').call,
1918
]));
2019
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter_devicelab/framework/framework.dart';
6+
import 'package:flutter_devicelab/tasks/plugin_tests.dart';
7+
8+
Future<void> main() async {
9+
await task(combine(<TaskFunction>[
10+
PluginTest('macos', <String>['--platforms=macos']).call,
11+
// Test that Dart-only plugins are supported.
12+
PluginTest('macos', <String>['--platforms=macos'], dartOnlyPlugin: true).call,
13+
// Test that shared darwin directories are supported.
14+
PluginTest('macos', <String>['--platforms=ios,macos'], sharedDarwinSource: true).call,
15+
// Test that FFI plugins are supported.
16+
PluginTest('macos', <String>['--platforms=macos'], template: 'plugin_ffi').call,
17+
]));
18+
}

dev/devicelab/lib/tasks/plugin_tests.dart

+82
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ class PluginTest {
3333
this.pluginCreateEnvironment,
3434
this.appCreateEnvironment,
3535
this.dartOnlyPlugin = false,
36+
this.sharedDarwinSource = false,
3637
this.template = 'plugin',
3738
});
3839

@@ -41,6 +42,7 @@ class PluginTest {
4142
final Map<String, String>? pluginCreateEnvironment;
4243
final Map<String, String>? appCreateEnvironment;
4344
final bool dartOnlyPlugin;
45+
final bool sharedDarwinSource;
4446
final String template;
4547

4648
Future<TaskResult> call() async {
@@ -58,6 +60,9 @@ class PluginTest {
5860
if (dartOnlyPlugin) {
5961
await plugin.convertDefaultPluginToDartPlugin();
6062
}
63+
if (sharedDarwinSource) {
64+
await plugin.convertDefaultPluginToSharedDarwinPlugin();
65+
}
6166
section('Test plugin');
6267
if (runFlutterTest) {
6368
await plugin.runFlutterTest();
@@ -159,6 +164,83 @@ class $dartPluginClass {
159164
}
160165
}
161166

167+
/// Converts an iOS/macOS plugin created from the standard template to a shared
168+
/// darwin directory plugin.
169+
Future<void> convertDefaultPluginToSharedDarwinPlugin() async {
170+
// Convert the metadata.
171+
final File pubspec = pubspecFile;
172+
String pubspecContent = await pubspec.readAsString();
173+
const String originalIOSKey = '\n ios:\n';
174+
const String originalMacOSKey = '\n macos:\n';
175+
if (!pubspecContent.contains(originalIOSKey) || !pubspecContent.contains(originalMacOSKey)) {
176+
print(pubspecContent);
177+
throw TaskResult.failure('Missing expected darwin platform plugin keys');
178+
}
179+
pubspecContent = pubspecContent.replaceAll(
180+
originalIOSKey,
181+
'$originalIOSKey sharedDarwinSource: true\n'
182+
);
183+
pubspecContent = pubspecContent.replaceAll(
184+
originalMacOSKey,
185+
'$originalMacOSKey sharedDarwinSource: true\n'
186+
);
187+
await pubspec.writeAsString(pubspecContent, flush: true);
188+
189+
// Copy ios to darwin, and delete macos.
190+
final Directory iosDir = Directory(path.join(rootPath, 'ios'));
191+
final Directory darwinDir = Directory(path.join(rootPath, 'darwin'));
192+
recursiveCopy(iosDir, darwinDir);
193+
194+
await iosDir.delete(recursive: true);
195+
await Directory(path.join(rootPath, 'macos')).delete(recursive: true);
196+
197+
final File podspec = File(path.join(darwinDir.path, '$name.podspec'));
198+
String podspecContent = await podspec.readAsString();
199+
if (!podspecContent.contains('s.platform =')) {
200+
print(podspecContent);
201+
throw TaskResult.failure('Missing expected podspec platform');
202+
}
203+
204+
// Remove "s.platform = :ios" to work on all platforms, including macOS.
205+
podspecContent = podspecContent.replaceFirst(RegExp(r'.*s\.platform.*'), '');
206+
podspecContent = podspecContent.replaceFirst("s.dependency 'Flutter'", "s.ios.dependency 'Flutter'\ns.osx.dependency 'FlutterMacOS'");
207+
208+
await podspec.writeAsString(podspecContent, flush: true);
209+
210+
// Make PlugintestPlugin.swift compile on iOS and macOS with target conditionals.
211+
final String pluginClass = '${name[0].toUpperCase()}${name.substring(1)}Plugin';
212+
print('pluginClass: $pluginClass');
213+
final File pluginRegister = File(path.join(darwinDir.path, 'Classes', '$pluginClass.swift'));
214+
final String pluginRegisterContent = '''
215+
#if os(macOS)
216+
import FlutterMacOS
217+
#elseif os(iOS)
218+
import Flutter
219+
#endif
220+
221+
public class $pluginClass: NSObject, FlutterPlugin {
222+
public static func register(with registrar: FlutterPluginRegistrar) {
223+
#if os(macOS)
224+
let channel = FlutterMethodChannel(name: "$name", binaryMessenger: registrar.messenger)
225+
#elseif os(iOS)
226+
let channel = FlutterMethodChannel(name: "$name", binaryMessenger: registrar.messenger())
227+
#endif
228+
let instance = $pluginClass()
229+
registrar.addMethodCallDelegate(instance, channel: channel)
230+
}
231+
232+
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
233+
#if os(macOS)
234+
result("macOS " + ProcessInfo.processInfo.operatingSystemVersionString)
235+
#elseif os(iOS)
236+
result("iOS " + UIDevice.current.systemVersion)
237+
#endif
238+
}
239+
}
240+
''';
241+
await pluginRegister.writeAsString(pluginRegisterContent, flush: true);
242+
}
243+
162244
Future<void> runFlutterTest() async {
163245
await inDirectory(Directory(rootPath), () async {
164246
await flutter('test');

packages/flutter_tools/bin/podhelper.rb

+7-2
Original file line numberDiff line numberDiff line change
@@ -266,14 +266,19 @@ def flutter_install_plugin_pods(application_path = nil, relative_symlink_dir, pl
266266
plugin_name = plugin_hash['name']
267267
plugin_path = plugin_hash['path']
268268
has_native_build = plugin_hash.fetch('native_build', true)
269+
270+
# iOS and macOS code can be shared in "darwin" directory, otherwise
271+
# respectively in "ios" or "macos" directories.
272+
shared_darwin_source = plugin_hash.fetch('shared_darwin_source', false)
273+
platform_directory = shared_darwin_source ? 'darwin' : platform
269274
next unless plugin_name && plugin_path && has_native_build
270275
symlink = File.join(symlink_plugins_dir, plugin_name)
271276
File.symlink(plugin_path, symlink)
272277

273278
# Keep pod path relative so it can be checked into Podfile.lock.
274279
relative = flutter_relative_path_from_podfile(symlink)
275280

276-
pod plugin_name, path: File.join(relative, platform)
281+
pod plugin_name, path: File.join(relative, platform_directory)
277282
end
278283
end
279284

@@ -288,7 +293,7 @@ def flutter_parse_plugins_file(file, platform)
288293

289294
# dependencies_hash.dig('plugins', 'ios') not available until Ruby 2.3
290295
return [] unless dependencies_hash.has_key?('plugins')
291-
return [] unless dependencies_hash['plugins'].has_key?('ios')
296+
return [] unless dependencies_hash['plugins'].has_key?(platform)
292297
dependencies_hash['plugins'][platform] || []
293298
end
294299

packages/flutter_tools/lib/src/flutter_plugins.dart

+3
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ const String _kFlutterPluginsNameKey = 'name';
104104
const String _kFlutterPluginsPathKey = 'path';
105105
const String _kFlutterPluginsDependenciesKey = 'dependencies';
106106
const String _kFlutterPluginsHasNativeBuildKey = 'native_build';
107+
const String _kFlutterPluginsSharedDarwinSource = 'shared_darwin_source';
107108

108109
/// Filters [plugins] to those supported by [platformKey].
109110
List<Map<String, Object>> _filterPluginsByPlatform(List<Plugin> plugins, String platformKey) {
@@ -119,6 +120,8 @@ List<Map<String, Object>> _filterPluginsByPlatform(List<Plugin> plugins, String
119120
pluginInfo.add(<String, Object>{
120121
_kFlutterPluginsNameKey: plugin.name,
121122
_kFlutterPluginsPathKey: globals.fsUtils.escapePath(plugin.path),
123+
if (platformPlugin is DarwinPlugin && (platformPlugin as DarwinPlugin).sharedDarwinSource)
124+
_kFlutterPluginsSharedDarwinSource: (platformPlugin as DarwinPlugin).sharedDarwinSource,
122125
if (platformPlugin is NativeOrDartPlugin)
123126
_kFlutterPluginsHasNativeBuildKey: (platformPlugin as NativeOrDartPlugin).hasMethodChannel() || (platformPlugin as NativeOrDartPlugin).hasFfi(),
124127
_kFlutterPluginsDependenciesKey: <String>[...plugin.dependencies.where(pluginNames.contains)],

packages/flutter_tools/lib/src/platform_plugins.dart

+33-4
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ const String kFfiPlugin = 'ffiPlugin';
1919
// Constant for 'defaultPackage' key in plugin maps.
2020
const String kDefaultPackage = 'default_package';
2121

22+
/// Constant for 'sharedDarwinSource' key in plugin maps.
23+
/// Can be set for iOS and macOS plugins.
24+
const String kSharedDarwinSource = 'sharedDarwinSource';
25+
2226
/// Constant for 'supportedVariants' key in plugin maps.
2327
const String kSupportedVariants = 'supportedVariants';
2428

@@ -52,6 +56,11 @@ abstract class NativeOrDartPlugin {
5256
bool hasMethodChannel();
5357
}
5458

59+
abstract class DarwinPlugin {
60+
/// Indicates the iOS and macOS native code is shareable the subdirectory "darwin",
61+
bool get sharedDarwinSource;
62+
}
63+
5564
/// Contains parameters to template an Android plugin.
5665
///
5766
/// The [name] of the plugin is required. Additionally, either:
@@ -227,15 +236,17 @@ class AndroidPlugin extends PluginPlatform implements NativeOrDartPlugin {
227236
/// - the [dartPluginClass] that will be the entry point for the plugin's
228237
/// Dart code
229238
/// is required.
230-
class IOSPlugin extends PluginPlatform implements NativeOrDartPlugin {
239+
class IOSPlugin extends PluginPlatform implements NativeOrDartPlugin, DarwinPlugin {
231240
const IOSPlugin({
232241
required this.name,
233242
required this.classPrefix,
234243
this.pluginClass,
235244
this.dartPluginClass,
236245
bool? ffiPlugin,
237246
this.defaultPackage,
238-
}) : ffiPlugin = ffiPlugin ?? false;
247+
bool? sharedDarwinSource,
248+
}) : ffiPlugin = ffiPlugin ?? false,
249+
sharedDarwinSource = sharedDarwinSource ?? false;
239250

240251
factory IOSPlugin.fromYaml(String name, YamlMap yaml) {
241252
assert(validate(yaml)); // TODO(zanderso): https://github.com/flutter/flutter/issues/67241
@@ -246,6 +257,7 @@ class IOSPlugin extends PluginPlatform implements NativeOrDartPlugin {
246257
dartPluginClass: yaml[kDartPluginClass] as String?,
247258
ffiPlugin: yaml[kFfiPlugin] as bool?,
248259
defaultPackage: yaml[kDefaultPackage] as String?,
260+
sharedDarwinSource: yaml[kSharedDarwinSource] as bool?,
249261
);
250262
}
251263

@@ -256,6 +268,7 @@ class IOSPlugin extends PluginPlatform implements NativeOrDartPlugin {
256268
return yaml[kPluginClass] is String ||
257269
yaml[kDartPluginClass] is String ||
258270
yaml[kFfiPlugin] == true ||
271+
yaml[kSharedDarwinSource] == true ||
259272
yaml[kDefaultPackage] is String;
260273
}
261274

@@ -271,6 +284,11 @@ class IOSPlugin extends PluginPlatform implements NativeOrDartPlugin {
271284
final bool ffiPlugin;
272285
final String? defaultPackage;
273286

287+
/// Indicates the iOS native code is shareable with macOS in
288+
/// the subdirectory "darwin", otherwise in the subdirectory "ios".
289+
@override
290+
final bool sharedDarwinSource;
291+
274292
@override
275293
bool hasMethodChannel() => pluginClass != null;
276294

@@ -288,6 +306,7 @@ class IOSPlugin extends PluginPlatform implements NativeOrDartPlugin {
288306
if (pluginClass != null) 'class': pluginClass,
289307
if (dartPluginClass != null) kDartPluginClass : dartPluginClass,
290308
if (ffiPlugin) kFfiPlugin: true,
309+
if (sharedDarwinSource) kSharedDarwinSource: true,
291310
if (defaultPackage != null) kDefaultPackage : defaultPackage,
292311
};
293312
}
@@ -298,14 +317,16 @@ class IOSPlugin extends PluginPlatform implements NativeOrDartPlugin {
298317
/// The [name] of the plugin is required. Either [dartPluginClass] or
299318
/// [pluginClass] or [ffiPlugin] are required.
300319
/// [pluginClass] will be the entry point to the plugin's native code.
301-
class MacOSPlugin extends PluginPlatform implements NativeOrDartPlugin {
320+
class MacOSPlugin extends PluginPlatform implements NativeOrDartPlugin, DarwinPlugin {
302321
const MacOSPlugin({
303322
required this.name,
304323
this.pluginClass,
305324
this.dartPluginClass,
306325
bool? ffiPlugin,
307326
this.defaultPackage,
308-
}) : ffiPlugin = ffiPlugin ?? false;
327+
bool? sharedDarwinSource,
328+
}) : ffiPlugin = ffiPlugin ?? false,
329+
sharedDarwinSource = sharedDarwinSource ?? false;
309330

310331
factory MacOSPlugin.fromYaml(String name, YamlMap yaml) {
311332
assert(validate(yaml));
@@ -320,6 +341,7 @@ class MacOSPlugin extends PluginPlatform implements NativeOrDartPlugin {
320341
dartPluginClass: yaml[kDartPluginClass] as String?,
321342
ffiPlugin: yaml[kFfiPlugin] as bool?,
322343
defaultPackage: yaml[kDefaultPackage] as String?,
344+
sharedDarwinSource: yaml[kSharedDarwinSource] as bool?,
323345
);
324346
}
325347

@@ -330,6 +352,7 @@ class MacOSPlugin extends PluginPlatform implements NativeOrDartPlugin {
330352
return yaml[kPluginClass] is String ||
331353
yaml[kDartPluginClass] is String ||
332354
yaml[kFfiPlugin] == true ||
355+
yaml[kSharedDarwinSource] == true ||
333356
yaml[kDefaultPackage] is String;
334357
}
335358

@@ -341,6 +364,11 @@ class MacOSPlugin extends PluginPlatform implements NativeOrDartPlugin {
341364
final bool ffiPlugin;
342365
final String? defaultPackage;
343366

367+
/// Indicates the macOS native code is shareable with iOS in
368+
/// the subdirectory "darwin", otherwise in the subdirectory "macos".
369+
@override
370+
final bool sharedDarwinSource;
371+
344372
@override
345373
bool hasMethodChannel() => pluginClass != null;
346374

@@ -357,6 +385,7 @@ class MacOSPlugin extends PluginPlatform implements NativeOrDartPlugin {
357385
if (pluginClass != null) 'class': pluginClass,
358386
if (dartPluginClass != null) kDartPluginClass: dartPluginClass,
359387
if (ffiPlugin) kFfiPlugin: true,
388+
if (sharedDarwinSource) kSharedDarwinSource: true,
360389
if (defaultPackage != null) kDefaultPackage: defaultPackage,
361390
};
362391
}

0 commit comments

Comments
 (0)