// 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:async'; import 'dart:io'; import 'package:flutter_devicelab/framework/framework.dart'; import 'package:flutter_devicelab/framework/utils.dart'; import 'package:path/path.dart' as path; /// Tests that a plugin A can depend on platform code from a plugin B /// as long as plugin B is defined as a pub dependency of plugin A. /// /// This test fails when `flutter build apk` fails and the stderr from this command /// contains "Unresolved reference: plugin_b". Future<void> main() async { await task(() async { section('Find Java'); final String javaHome = await findJavaHome(); if (javaHome == null) { return TaskResult.failure('Could not find Java'); } print('\nUsing JAVA_HOME=$javaHome'); final Directory tempDir = Directory.systemTemp.createTempSync('flutter_plugin_dependencies.'); try { section('Create plugin A'); final Directory pluginADirectory = Directory(path.join(tempDir.path, 'plugin_a')); await inDirectory(tempDir, () async { await flutter( 'create', options: <String>[ '--org', 'io.flutter.devicelab.plugin_a', '--template=plugin', pluginADirectory.path, ], ); }); section('Create plugin B'); final Directory pluginBDirectory = Directory(path.join(tempDir.path, 'plugin_b')); await inDirectory(tempDir, () async { await flutter( 'create', options: <String>[ '--org', 'io.flutter.devicelab.plugin_b', '--template=plugin', pluginBDirectory.path, ], ); }); section('Create plugin C without android/ directory'); final Directory pluginCDirectory = Directory(path.join(tempDir.path, 'plugin_c')); await inDirectory(tempDir, () async { await flutter( 'create', options: <String>[ '--org', 'io.flutter.devicelab.plugin_c', '--template=plugin', pluginCDirectory.path, ], ); }); // https://github.com/flutter/flutter/issues/46898 // https://github.com/flutter/flutter/issues/39657 File(path.join(pluginCDirectory.path, 'android', 'build.gradle')).deleteSync(); final File pluginCpubspec = File(path.join(pluginCDirectory.path, 'pubspec.yaml')); await pluginCpubspec.writeAsString(''' name: plugin_c version: 0.0.1 flutter: plugin: platforms: ios: pluginClass: Plugin_cPlugin dependencies: flutter: sdk: flutter environment: sdk: ">=2.0.0-dev.28.0 <3.0.0" flutter: ">=1.5.0 <2.0.0" ''', flush: true); section('Write dummy Kotlin code in plugin B'); final File pluginBKotlinClass = File(path.join( pluginBDirectory.path, 'android', 'src', 'main', 'kotlin', 'DummyPluginBClass.kt', )); await pluginBKotlinClass.writeAsString(''' package io.flutter.devicelab.plugin_b public class DummyPluginBClass { companion object { fun dummyStaticMethod() { } } } ''', flush: true); section('Make plugin A depend on plugin B and plugin C'); final File pluginApubspec = File(path.join(pluginADirectory.path, 'pubspec.yaml')); String pluginApubspecContent = await pluginApubspec.readAsString(); pluginApubspecContent = pluginApubspecContent.replaceFirst( '\ndependencies:\n', '\ndependencies:\n' ' plugin_b:\n' ' path: ${pluginBDirectory.path}\n' ' plugin_c:\n' ' path: ${pluginCDirectory.path}\n', ); await pluginApubspec.writeAsString(pluginApubspecContent, flush: true); section('Write Kotlin code in plugin A that references Kotlin code from plugin B'); final File pluginAKotlinClass = File(path.join( pluginADirectory.path, 'android', 'src', 'main', 'kotlin', 'DummyPluginAClass.kt', )); await pluginAKotlinClass.writeAsString(''' package io.flutter.devicelab.plugin_a import io.flutter.devicelab.plugin_b.DummyPluginBClass public class DummyPluginAClass { constructor() { // Call a method from plugin b. DummyPluginBClass.dummyStaticMethod(); } } ''', flush: true); section('Verify .flutter-plugins-dependencies'); final Directory exampleApp = Directory(path.join(pluginADirectory.path, 'example')); await inDirectory(exampleApp, () async { await flutter( 'packages', options: <String>['get'], ); }); final File flutterPluginsDependenciesFile = File(path.join(exampleApp.path, '.flutter-plugins-dependencies')); if (!flutterPluginsDependenciesFile.existsSync()) { return TaskResult.failure('${flutterPluginsDependenciesFile.path} doesn\'t exist'); } final String flutterPluginsDependenciesFileContent = flutterPluginsDependenciesFile.readAsStringSync(); const String kExpectedPluginsDependenciesContent = '{' '\"_info\":\"// This is a generated file; do not edit or check into version control.\",' '\"dependencyGraph\":[' '{' '\"name\":\"plugin_a\",' '\"dependencies\":[\"plugin_b\",\"plugin_c\"]' '},' '{' '\"name\":\"plugin_b\",' '\"dependencies\":[]' '},' '{' '\"name\":\"plugin_c\",' '\"dependencies\":[]' '}' ']' '}'; if (flutterPluginsDependenciesFileContent != kExpectedPluginsDependenciesContent) { return TaskResult.failure( 'Unexpected file content in ${flutterPluginsDependenciesFile.path}: ' 'Found "$flutterPluginsDependenciesFileContent" instead of ' '"$kExpectedPluginsDependenciesContent"' ); } section('Build plugin A example app'); final StringBuffer stderr = StringBuffer(); await inDirectory(exampleApp, () async { await evalFlutter( 'build', options: <String>['apk', '--target-platform', 'android-arm'], canFail: true, stderr: stderr, ); }); if (stderr.toString().contains('Unresolved reference: plugin_b')) { return TaskResult.failure('plugin_a cannot reference plugin_b'); } final bool pluginAExampleApk = exists(File(path.join( pluginADirectory.path, 'example', 'build', 'app', 'outputs', 'apk', 'release', 'app-release.apk', ))); if (!pluginAExampleApk) { return TaskResult.failure('Failed to build plugin A example APK'); } return TaskResult.success(null); } on TaskResult catch (taskResult) { return taskResult; } catch (e) { return TaskResult.failure(e.toString()); } finally { rmTree(tempDir); } }); }