// 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 'dart:io'; import 'package:flutter_devicelab/framework/framework.dart'; import 'package:flutter_devicelab/framework/task_result.dart'; import 'package:flutter_devicelab/framework/utils.dart'; import 'package:path/path.dart' as path; /// Tests that the Flutter plugin template works. Use `pod lib lint` /// to confirm the plugin module can be imported into an app. Future<void> main() async { await task(() async { final Directory tempDir = Directory.systemTemp.createTempSync('flutter_plugin_test.'); try { section('Lint integration_test'); await inDirectory(tempDir, () async { // Update pod repo. await exec( 'pod', <String>['repo', 'update'], ); // Relative to this script. final String flutterRoot = path.dirname(path.dirname(path.dirname(path.dirname(path.dirname(path.fromUri(Platform.script)))))); print('Flutter root at $flutterRoot'); final String integrationTestPackage = path.join(flutterRoot, 'packages', 'integration_test'); final String iosintegrationTestPodspec = path.join(integrationTestPackage, 'ios', 'integration_test.podspec'); await exec( 'pod', <String>[ 'lib', 'lint', iosintegrationTestPodspec, '--use-libraries', '--verbose', ], ); final String macosintegrationTestPodspec = path.join(integrationTestPackage, 'integration_test_macos', 'macos', 'integration_test_macos.podspec'); await exec( 'pod', <String>[ 'lib', 'lint', macosintegrationTestPodspec, '--verbose', ], ); }); section('Create Objective-C plugin'); const String objcPluginName = 'test_plugin_objc'; await inDirectory(tempDir, () async { await flutter( 'create', options: <String>[ '--org', 'io.flutter.devicelab', '--template=plugin', '--platforms=ios,android', '--ios-language=objc', objcPluginName, ], ); }); section('Lint Objective-C iOS podspec plugin as framework'); final String objcPluginPath = path.join(tempDir.path, objcPluginName); final String objcPodspecPath = path.join(objcPluginPath, 'ios', '$objcPluginName.podspec'); await inDirectory(tempDir, () async { await exec( 'pod', <String>[ 'lib', 'lint', objcPodspecPath, '--allow-warnings', '--verbose', ], ); }); section('Lint Objective-C iOS podspec plugin as library'); await inDirectory(tempDir, () async { await exec( 'pod', <String>[ 'lib', 'lint', objcPodspecPath, '--allow-warnings', '--use-libraries', '--verbose', ], ); }); section('Create Swift plugin'); const String swiftPluginName = 'test_plugin_swift'; await inDirectory(tempDir, () async { await flutter( 'create', options: <String>[ '--org', 'io.flutter.devicelab', '--template=plugin', '--platforms=ios,macos', '--ios-language=swift', swiftPluginName, ], ); }); section('Lint Swift iOS podspec plugin as framework'); final String swiftPluginPath = path.join(tempDir.path, swiftPluginName); final String swiftPodspecPath = path.join(swiftPluginPath, 'ios', '$swiftPluginName.podspec'); await inDirectory(tempDir, () async { await exec( 'pod', <String>[ 'lib', 'lint', swiftPodspecPath, '--allow-warnings', '--verbose', ], ); }); section('Lint Swift iOS podspec plugin as library'); await inDirectory(tempDir, () async { await exec( 'pod', <String>[ 'lib', 'lint', swiftPodspecPath, '--allow-warnings', '--use-libraries', '--verbose', ], ); }); section('Lint Swift macOS podspec plugin as framework'); final String macOSPodspecPath = path.join(swiftPluginPath, 'macos', '$swiftPluginName.podspec'); await inDirectory(tempDir, () async { await exec( 'pod', <String>[ 'lib', 'lint', macOSPodspecPath, '--allow-warnings', '--verbose', ], ); }); section('Lint Swift macOS podspec plugin as library'); await inDirectory(tempDir, () async { await exec( 'pod', <String>[ 'lib', 'lint', macOSPodspecPath, '--allow-warnings', '--use-libraries', '--verbose', ], ); }); section('Create Objective-C application'); const String objcAppName = 'test_app_objc'; await inDirectory(tempDir, () async { await flutter( 'create', options: <String>[ '--org', 'io.flutter.devicelab', '--platforms=ios', '--ios-language=objc', objcAppName, ], ); }); section('Build Objective-C application with Swift and Objective-C plugins as libraries'); final String objcAppPath = path.join(tempDir.path, objcAppName); final File objcPubspec = File(path.join(objcAppPath, 'pubspec.yaml')); String pubspecContent = objcPubspec.readAsStringSync(); // Add (randomly selected) first-party plugins that support iOS and macOS. // Add the new plugins we just made. pubspecContent = pubspecContent.replaceFirst( '\ndependencies:\n', '\ndependencies:\n $objcPluginName:\n path: $objcPluginPath\n $swiftPluginName:\n path: $swiftPluginPath\n url_launcher: 6.0.16\n url_launcher_macos:\n', ); objcPubspec.writeAsStringSync(pubspecContent, flush: true); await inDirectory(objcAppPath, () async { await flutter( 'build', options: <String>[ 'ios', '--no-codesign', ], // TODO(jmagman): Make Objective-C applications handle Swift libraries https://github.com/flutter/flutter/issues/16049 canFail: true ); }); final File objcPodfile = File(path.join(objcAppPath, 'ios', 'Podfile')); String objcPodfileContent = objcPodfile.readAsStringSync(); if (objcPodfileContent.contains('use_frameworks!')) { return TaskResult.failure('Expected default Objective-C Podfile to not contain use_frameworks'); } _validateIosPodfile(objcAppPath); section('Build Objective-C application with Swift and Objective-C plugins as frameworks'); objcPodfileContent = 'use_frameworks!\n$objcPodfileContent'; objcPodfile.writeAsStringSync(objcPodfileContent, flush: true); await inDirectory(objcAppPath, () async { await flutter( 'build', options: <String>[ 'ios', '--no-codesign', ], ); }); section('Create Swift application'); const String swiftAppName = 'test_app_swift'; await inDirectory(tempDir, () async { await flutter( 'create', options: <String>[ '--org', 'io.flutter.devicelab', '--platforms=ios,macos', '--ios-language=swift', swiftAppName, ], ); }); section('Build Swift iOS application with Swift and Objective-C plugins as frameworks'); final String swiftAppPath = path.join(tempDir.path, swiftAppName); final File swiftPubspec = File(path.join(swiftAppPath, 'pubspec.yaml')); swiftPubspec.writeAsStringSync(pubspecContent, flush: true); await inDirectory(swiftAppPath, () async { await flutter( 'build', options: <String>[ 'ios', '--no-codesign', ], ); }); final File swiftPodfile = File(path.join(swiftAppPath, 'ios', 'Podfile')); String swiftPodfileContent = swiftPodfile.readAsStringSync(); if (!swiftPodfileContent.contains('use_frameworks!')) { return TaskResult.failure('Expected default Swift Podfile to contain use_frameworks'); } section('Build Swift iOS application with Swift and Objective-C plugins as libraries'); swiftPodfileContent = swiftPodfileContent.replaceAll('use_frameworks!', ''); swiftPodfile.writeAsStringSync(swiftPodfileContent, flush: true); await inDirectory(swiftAppPath, () async { await flutter( 'build', options: <String>[ 'ios', '--no-codesign', ], ); }); _validateIosPodfile(swiftAppPath); section('Build Swift macOS application with plugins as frameworks'); await inDirectory(swiftAppPath, () async { await flutter( 'build', options: <String>[ 'macos', ], ); }); final File macOSPodfile = File(path.join(swiftAppPath, 'macos', 'Podfile')); String macosPodfileContent = macOSPodfile.readAsStringSync(); if (!macosPodfileContent.contains('use_frameworks!')) { return TaskResult.failure('Expected default Swift Podfile to contain use_frameworks'); } _validateMacOSPodfile(swiftAppPath); section('Build Swift macOS application with plugins as libraries'); macosPodfileContent = macosPodfileContent.replaceAll('use_frameworks!', ''); macOSPodfile.writeAsStringSync(macosPodfileContent, flush: true); await inDirectory(swiftAppPath, () async { await flutter( 'build', options: <String>[ 'macos', ], ); }); _validateMacOSPodfile(swiftAppPath); section('Remove iOS support from plugin'); Directory(path.join(objcPluginPath, 'ios')).deleteSync(recursive: true); const String iosPlatformMap = ''' ios: pluginClass: TestPluginObjcPlugin'''; final File pluginPubspec = File(path.join(objcPluginPath, 'pubspec.yaml')); String pluginPubspecContent = pluginPubspec.readAsStringSync(); if (!pluginPubspecContent.contains(iosPlatformMap)) { return TaskResult.failure('Plugin pubspec.yaml missing iOS platform map'); } pluginPubspecContent = pluginPubspecContent.replaceFirst(iosPlatformMap, ''); pluginPubspec.writeAsStringSync(pluginPubspecContent, flush: true); await inDirectory(swiftAppPath, () async { await flutter('clean'); await flutter( 'build', options: <String>[ 'ios', '--no-codesign', ], ); }); section('Validate plugin without iOS platform'); final File podfileLockFile = File(path.join(swiftAppPath, 'ios', 'Podfile.lock')); final String podfileLockOutput = podfileLockFile.readAsStringSync(); if (!podfileLockOutput.contains(':path: ".symlinks/plugins/url_launcher_ios/ios"') || !podfileLockOutput.contains(':path: Flutter') // test_plugin_objc no longer supports iOS, shouldn't be present. || podfileLockOutput.contains(':path: ".symlinks/plugins/test_plugin_objc/ios"') || !podfileLockOutput.contains(':path: ".symlinks/plugins/test_plugin_swift/ios"')) { print(podfileLockOutput); return TaskResult.failure('Podfile.lock does not contain expected pods'); } final String pluginSymlinks = path.join( swiftAppPath, 'ios', '.symlinks', 'plugins', ); checkDirectoryExists(path.join( pluginSymlinks, 'url_launcher_ios', 'ios', )); checkDirectoryExists(path.join( pluginSymlinks, 'test_plugin_swift', 'ios', )); // test_plugin_objc no longer supports iOS, shouldn't exist! checkDirectoryNotExists(path.join( pluginSymlinks, 'test_plugin_objc', )); return TaskResult.success(null); } catch (e) { return TaskResult.failure(e.toString()); } finally { rmTree(tempDir); } }); } void _validateIosPodfile(String appPath) { section('Validate iOS Podfile'); final File podfileLockFile = File(path.join(appPath, 'ios', 'Podfile.lock')); final String podfileLockOutput = podfileLockFile.readAsStringSync(); if (!podfileLockOutput.contains(':path: ".symlinks/plugins/url_launcher_ios/ios"') || !podfileLockOutput.contains(':path: Flutter') || !podfileLockOutput.contains(':path: ".symlinks/plugins/test_plugin_objc/ios"') || !podfileLockOutput.contains(':path: ".symlinks/plugins/test_plugin_swift/ios"') || podfileLockOutput.contains('url_launcher_macos')) { print(podfileLockOutput); throw TaskResult.failure('iOS Podfile.lock does not contain expected pods'); } checkDirectoryNotExists(path.join( appPath, 'ios', 'Flutter', 'Flutter.framework', )); checkFileExists(path.join( appPath, 'ios', 'Flutter', 'Flutter.podspec', )); final String pluginSymlinks = path.join( appPath, 'ios', '.symlinks', 'plugins', ); checkDirectoryExists(path.join( pluginSymlinks, 'url_launcher_ios', 'ios', )); checkDirectoryNotExists(path.join( pluginSymlinks, 'url_launcher_macos', )); checkDirectoryExists(path.join( pluginSymlinks, 'test_plugin_objc', 'ios', )); checkDirectoryExists(path.join( pluginSymlinks, 'test_plugin_swift', 'ios', )); // Make sure no Xcode build settings are leaking derived data/build directory into the ios directory. checkDirectoryNotExists(path.join( appPath, 'ios', 'build', )); } void _validateMacOSPodfile(String appPath) { section('Validate macOS Podfile'); final File podfileLockFile = File(path.join(appPath, 'macos', 'Podfile.lock')); final String podfileLockOutput = podfileLockFile.readAsStringSync(); if (!podfileLockOutput.contains(':path: Flutter/ephemeral\n') || !podfileLockOutput.contains(':path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos') || !podfileLockOutput.contains(':path: Flutter/ephemeral/.symlinks/plugins/test_plugin_swift/macos') || podfileLockOutput.contains('url_launcher_ios/')) { print(podfileLockOutput); throw TaskResult.failure('macOS Podfile.lock does not contain expected pods'); } checkFileExists(path.join( appPath, 'macos', 'Flutter', 'ephemeral', 'FlutterMacOS.podspec', )); final String pluginSymlinks = path.join( appPath, 'macos', 'Flutter', 'ephemeral', '.symlinks', 'plugins', ); checkDirectoryExists(path.join( pluginSymlinks, 'url_launcher_macos', 'macos', )); checkDirectoryNotExists(path.join( pluginSymlinks, 'url_launcher_ios', )); checkDirectoryExists(path.join( pluginSymlinks, 'test_plugin_swift', 'macos', )); }