blob: 26d8dada3afaf9ebff227c2be8d754ccec2329fe [file] [log] [blame]
// 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:convert';
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;
final String platformLineSep = Platform.isWindows ? '\r\n': '\n';
/// 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',
'--platforms=android,ios',
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',
'--platforms=android,ios',
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',
'--platforms=ios',
pluginCDirectory.path,
],
);
});
checkDirectoryNotExists(path.join(
pluginCDirectory.path,
'android',
));
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"
''', flush: true);
section('Create plugin D without ios/ directory');
final Directory pluginDDirectory = Directory(path.join(tempDir.path, 'plugin_d'));
await inDirectory(tempDir, () async {
await flutter(
'create',
options: <String>[
'--org',
'io.flutter.devicelab.plugin_d',
'--template=plugin',
'--platforms=android',
pluginDDirectory.path,
],
);
});
checkDirectoryNotExists(path.join(
pluginDDirectory.path,
'ios',
));
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, C, and D');
final File pluginApubspec = File(path.join(pluginADirectory.path, 'pubspec.yaml'));
String pluginApubspecContent = await pluginApubspec.readAsString();
pluginApubspecContent = pluginApubspecContent.replaceFirst(
'${platformLineSep}dependencies:$platformLineSep',
'${platformLineSep}dependencies:$platformLineSep'
' plugin_b:$platformLineSep'
' path: ${pluginBDirectory.path}$platformLineSep'
' plugin_c:$platformLineSep'
' path: ${pluginCDirectory.path}$platformLineSep'
' plugin_d:$platformLineSep'
' path: ${pluginDDirectory.path}$platformLineSep',
);
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();
final Map<String, dynamic> jsonContent = json.decode(flutterPluginsDependenciesFileContent) as Map<String, dynamic>;
// Verify the dependencyGraph object is valid. The rest of the contents of this file are not relevant to the
// dependency graph and are tested by unit tests.
final List<dynamic> dependencyGraph = jsonContent['dependencyGraph'] as List<dynamic>;
const String kExpectedPluginsDependenciesContent =
'['
'{'
'"name":"plugin_a",'
'"dependencies":["plugin_b","plugin_c","plugin_d"]'
'},'
'{'
'"name":"plugin_b",'
'"dependencies":[]'
'},'
'{'
'"name":"plugin_c",'
'"dependencies":[]'
'},'
'{'
'"name":"plugin_d",'
'"dependencies":[]'
'}'
']';
final String graphString = json.encode(dependencyGraph);
if (graphString != kExpectedPluginsDependenciesContent) {
return TaskResult.failure(
'Unexpected file content in ${flutterPluginsDependenciesFile.path}: '
'Found "$graphString" instead of "$kExpectedPluginsDependenciesContent"'
);
}
section('Build plugin A example Android 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');
}
if (Platform.isMacOS) {
section('Build plugin A example iOS app');
await inDirectory(exampleApp, () async {
await evalFlutter(
'build',
options: <String>[
'ios',
'--no-codesign',
],
);
});
final Directory appBundle = Directory(path.join(
pluginADirectory.path,
'example',
'build',
'ios',
'iphoneos',
'Runner.app',
));
if (!exists(appBundle)) {
return TaskResult.failure('Failed to build plugin A example iOS app');
}
checkDirectoryExists(path.join(
appBundle.path,
'Frameworks',
'plugin_a.framework',
));
checkDirectoryExists(path.join(
appBundle.path,
'Frameworks',
'plugin_b.framework',
));
checkDirectoryExists(path.join(
appBundle.path,
'Frameworks',
'plugin_c.framework',
));
// Plugin D is Android only and should not be embedded.
checkDirectoryNotExists(path.join(
appBundle.path,
'Frameworks',
'plugin_d.framework',
));
}
return TaskResult.success(null);
} on TaskResult catch (taskResult) {
return taskResult;
} catch (e) {
return TaskResult.failure(e.toString());
} finally {
rmTree(tempDir);
}
});
}