Unverified Commit d4e5e1e1 authored by Mikkel Nygaard Ravn's avatar Mikkel Nygaard Ravn Committed by GitHub

Materialize Flutter module, Android (#20520)

parent f17bb519
......@@ -40,7 +40,12 @@ Future<Null> main() async {
'\ndependencies:\n battery:\n package_info:\n',
);
await pubspec.writeAsString(content, flush: true);
await inDirectory(new Directory(path.join(directory.path, 'hello')), () async {
await flutter(
'packages',
options: <String>['get'],
);
});
section('Build Flutter module library archive');
......@@ -76,7 +81,7 @@ Future<Null> main() async {
);
});
final bool apkBuilt = exists(new File(path.join(
final bool ephemeralHostApkBuilt = exists(new File(path.join(
directory.path,
'hello',
'build',
......@@ -87,10 +92,49 @@ Future<Null> main() async {
'app-release.apk',
)));
if (!apkBuilt) {
if (!ephemeralHostApkBuilt) {
return new TaskResult.failure('Failed to build ephemeral host .apk');
}
section('Clean build');
await inDirectory(new Directory(path.join(directory.path, 'hello')), () async {
await flutter('clean');
});
section('Materialize host app');
await inDirectory(new Directory(path.join(directory.path, 'hello')), () async {
await flutter(
'materialize',
options: <String>['android'],
);
});
section('Build materialized host app');
await inDirectory(new Directory(path.join(directory.path, 'hello')), () async {
await flutter(
'build',
options: <String>['apk'],
);
});
final bool materializedHostApkBuilt = exists(new File(path.join(
directory.path,
'hello',
'build',
'host',
'outputs',
'apk',
'release',
'app-release.apk',
)));
if (!materializedHostApkBuilt) {
return new TaskResult.failure('Failed to build materialized host .apk');
}
section('Add to Android app');
final Directory hostApp = new Directory(path.join(directory.path, 'hello_host_app'));
......
......@@ -23,6 +23,7 @@ import 'src/commands/ide_config.dart';
import 'src/commands/inject_plugins.dart';
import 'src/commands/install.dart';
import 'src/commands/logs.dart';
import 'src/commands/materialize.dart';
import 'src/commands/packages.dart';
import 'src/commands/precache.dart';
import 'src/commands/run.dart';
......@@ -67,6 +68,7 @@ Future<Null> main(List<String> args) async {
new InjectPluginsCommand(hidden: !verboseHelp),
new InstallCommand(),
new LogsCommand(),
new MaterializeCommand(),
new PackagesCommand(),
new PrecacheCommand(),
new RunCommand(verboseHelp: verboseHelp),
......
......@@ -23,7 +23,7 @@ import '../project.dart';
import 'android_sdk.dart';
import 'android_studio.dart';
const String gradleVersion = '4.1';
const String gradleVersion = '4.4';
final RegExp _assembleTaskPattern = new RegExp(r'assemble([^:]+): task ');
GradleProject _cachedGradleProject;
......@@ -44,7 +44,7 @@ final RegExp ndkMessageFilter = new RegExp(r'^(?!NDK is missing a ".*" directory
r'|If you are using NDK, verify the ndk.dir is set to a valid NDK directory. It is currently set to .*)');
FlutterPluginVersion getFlutterPluginVersion(AndroidProject project) {
final File plugin = project.directory.childFile(
final File plugin = project.hostAppGradleRoot.childFile(
fs.path.join('buildSrc', 'src', 'main', 'groovy', 'FlutterPlugin.groovy'));
if (plugin.existsSync()) {
final String packageLine = plugin.readAsLinesSync().skip(4).first;
......@@ -53,8 +53,8 @@ FlutterPluginVersion getFlutterPluginVersion(AndroidProject project) {
}
return FlutterPluginVersion.v1;
}
final File appGradle = project.directory.childFile(
fs.path.join('app','build.gradle'));
final File appGradle = project.hostAppGradleRoot.childFile(
fs.path.join('app', 'build.gradle'));
if (appGradle.existsSync()) {
for (String line in appGradle.readAsLinesSync()) {
if (line.contains(new RegExp(r'apply from: .*/flutter.gradle'))) {
......@@ -93,13 +93,13 @@ Future<GradleProject> _gradleProject() async {
Future<GradleProject> _readGradleProject() async {
final FlutterProject flutterProject = await FlutterProject.current();
final String gradle = await _ensureGradle(flutterProject);
await updateLocalProperties(project: flutterProject);
updateLocalProperties(project: flutterProject);
final Status status = logger.startProgress('Resolving dependencies...', expectSlowOperation: true);
GradleProject project;
try {
final RunResult runResult = await runCheckedAsync(
<String>[gradle, 'app:properties'],
workingDirectory: flutterProject.android.directory.path,
workingDirectory: flutterProject.android.hostAppGradleRoot.path,
environment: _gradleEnv,
);
final String properties = runResult.stdout.trim();
......@@ -166,7 +166,7 @@ Future<String> _ensureGradle(FlutterProject project) async {
// Note: Gradle may be bootstrapped and possibly downloaded as a side-effect
// of validating the Gradle executable. This may take several seconds.
Future<String> _initializeGradle(FlutterProject project) async {
final Directory android = project.android.directory;
final Directory android = project.android.hostAppGradleRoot;
final Status status = logger.startProgress('Initializing gradle...', expectSlowOperation: true);
String gradle = _locateGradlewExecutable(android);
if (gradle == null) {
......@@ -205,13 +205,13 @@ distributionUrl=https\\://services.gradle.org/distributions/gradle-$gradleVersio
///
/// If [requireAndroidSdk] is true (the default) and no Android SDK is found,
/// this will fail with a [ToolExit].
Future<void> updateLocalProperties({
void updateLocalProperties({
@required FlutterProject project,
BuildInfo buildInfo,
bool requireAndroidSdk = true,
}) async {
if (requireAndroidSdk && androidSdk == null) {
throwToolExit('Unable to locate Android SDK. Please run `flutter doctor` for more details.');
}) {
if (requireAndroidSdk) {
_exitIfNoAndroidSdk();
}
final File localProperties = project.android.localPropertiesFile;
......@@ -250,6 +250,24 @@ Future<void> updateLocalProperties({
settings.writeContents(localProperties);
}
/// Writes standard Android local properties to the specified [properties] file.
///
/// Writes the path to the Android SDK, if known.
void writeLocalProperties(File properties) {
final SettingsFile settings = new SettingsFile();
if (androidSdk != null) {
settings.values['sdk.dir'] = escapePath(androidSdk.directory);
}
settings.writeContents(properties);
}
/// Throws a ToolExit, if the path to the Android SDK is not known.
void _exitIfNoAndroidSdk() {
if (androidSdk == null) {
throwToolExit('Unable to locate Android SDK. Please run `flutter doctor` for more details.');
}
}
Future<Null> buildGradleProject({
@required FlutterProject project,
@required BuildInfo buildInfo,
......@@ -263,7 +281,7 @@ Future<Null> buildGradleProject({
// and can be overwritten with flutter build command.
// The default Gradle script reads the version name and number
// from the local.properties file.
await updateLocalProperties(project: project, buildInfo: buildInfo);
updateLocalProperties(project: project, buildInfo: buildInfo);
final String gradle = await _ensureGradle(project);
......@@ -284,7 +302,7 @@ Future<Null> _buildGradleProjectV1(FlutterProject project, String gradle) async
final Status status = logger.startProgress('Running \'gradlew build\'...', expectSlowOperation: true);
final int exitCode = await runCommandAndStreamOutput(
<String>[fs.file(gradle).absolute.path, 'build'],
workingDirectory: project.android.directory.path,
workingDirectory: project.android.hostAppGradleRoot.path,
allowReentrantFlutter: true,
environment: _gradleEnv,
);
......@@ -361,7 +379,7 @@ Future<Null> _buildGradleProjectV2(
command.add(assembleTask);
final int exitCode = await runCommandAndStreamOutput(
command,
workingDirectory: flutterProject.android.directory.path,
workingDirectory: flutterProject.android.hostAppGradleRoot.path,
allowReentrantFlutter: true,
environment: _gradleEnv,
filter: logger.isVerbose ? null : ndkMessageFilter,
......
......@@ -107,7 +107,7 @@ class AndroidApk extends ApplicationPackage {
apkFile = fs.file(fs.path.join(getAndroidBuildDirectory(), 'app.apk'));
}
final File manifest = androidProject.gradleManifestFile;
final File manifest = androidProject.appManifestFile;
if (!manifest.existsSync())
return null;
......
......@@ -286,8 +286,7 @@ To edit platform code in an IDE see https://flutter.io/developing-packages/#edit
);
}
final FlutterProject project = await FlutterProject.fromDirectory(directory);
if (android_sdk.androidSdk != null)
await gradle.updateLocalProperties(project: project);
gradle.updateLocalProperties(project: project, requireAndroidSdk: false);
final String projectName = templateContext['projectName'];
final String organization = templateContext['organization'];
......@@ -320,8 +319,7 @@ To edit platform code in an IDE see https://flutter.io/developing-packages/#edit
await project.ensureReadyForPlatformSpecificTooling();
}
if (android_sdk.androidSdk != null)
await gradle.updateLocalProperties(project: project);
gradle.updateLocalProperties(project: project, requireAndroidSdk: false);
return generatedCount;
}
......@@ -373,7 +371,7 @@ To edit platform code in an IDE see https://flutter.io/developing-packages/#edit
int filesCreated = 0;
copyDirectorySync(
cache.getArtifactDirectory('gradle_wrapper'),
project.android.directory,
project.android.hostAppGradleRoot,
(File sourceFile, File destinationFile) {
filesCreated++;
final String modes = sourceFile.statSync().modeString();
......
// Copyright 2018 The Chromium 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 'package:meta/meta.dart';
import '../base/common.dart';
import '../project.dart';
import '../runner/flutter_command.dart';
class MaterializeCommand extends FlutterCommand {
MaterializeCommand() {
addSubcommand(new MaterializeAndroidCommand());
addSubcommand(new MaterializeIosCommand());
}
@override
final String name = 'materialize';
@override
final String description = 'Commands for materializing host apps for a Flutter Module';
@override
bool get hidden => true;
@override
Future<Null> runCommand() async { }
}
abstract class MaterializeSubCommand extends FlutterCommand {
MaterializeSubCommand() {
requiresPubspecYaml();
}
FlutterProject _project;
@override
@mustCallSuper
Future<Null> runCommand() async {
await _project.ensureReadyForPlatformSpecificTooling();
}
@override
Future<Null> validateCommand() async {
await super.validateCommand();
_project = await FlutterProject.current();
if (!_project.isModule)
throw new ToolExit("Only projects created using 'flutter create -t module' can be materialized.");
}
}
class MaterializeAndroidCommand extends MaterializeSubCommand {
@override
String get name => 'android';
@override
String get description => 'Materialize an Android host app';
@override
Future<Null> runCommand() async {
await super.runCommand();
await _project.android.materialize();
}
}
class MaterializeIosCommand extends MaterializeSubCommand {
@override
String get name => 'ios';
@override
String get description => 'Materialize an iOS host app';
@override
Future<Null> runCommand() async {
await super.runCommand();
await _project.ios.materialize();
}
}
......@@ -329,7 +329,7 @@ class _DevFSHttpWriter {
}
class DevFS {
/// Create a [DevFS] named [fsName] for the local files in [directory].
/// Create a [DevFS] named [fsName] for the local files in [rootDirectory].
DevFS(VMService serviceProtocol,
this.fsName,
this.rootDirectory, {
......
......@@ -105,7 +105,7 @@ bool _writeFlutterPluginsList(FlutterProject project, List<Plugin> plugins) {
return oldContents != newContents;
}
/// Returns the contents of the `.flutter-plugins` file in [directory], or
/// Returns the contents of the `.flutter-plugins` file in [project], or
/// null if that file does not exist.
String _readFlutterPluginsList(FlutterProject project) {
return project.flutterPluginsFile.existsSync()
......@@ -295,8 +295,7 @@ Future<void> injectPlugins(FlutterProject project) async {
}
}
/// Returns whether the Flutter project at the specified [directory]
/// has any plugin dependencies.
/// Returns whether the specified Flutter [project] has any plugin dependencies.
bool hasPlugins(FlutterProject project) {
return _readFlutterPluginsList(project) != null;
}
......@@ -186,6 +186,10 @@ class IosProject {
}
}
Future<void> materialize() async {
throwToolExit('flutter materialize has not yet been implemented for iOS');
}
bool _shouldRegenerateFromTemplate() {
return Cache.instance.fileOlderThanToolsStamp(directory.childFile('podhelper.rb'));
}
......@@ -212,62 +216,112 @@ class AndroidProject {
/// The parent of this project.
final FlutterProject parent;
/// The directory of this project.
Directory get directory => parent.directory.childDirectory(isModule ? '.android' : 'android');
/// The Gradle root directory of the Android host app. This is the directory
/// containing the `app/` subdirectory and the `settings.gradle` file that
/// includes it in the overall Gradle project.
Directory get hostAppGradleRoot {
if (!isModule || _materializedDirectory.existsSync())
return _materializedDirectory;
return _ephemeralDirectory;
}
/// The Gradle root directory of the Android wrapping of Flutter and plugins.
/// This is the same as [hostAppGradleRoot] except when the project is
/// a Flutter module with a materialized host app.
Directory get _flutterLibGradleRoot => isModule ? _ephemeralDirectory : _materializedDirectory;
Directory get _ephemeralDirectory => parent.directory.childDirectory('.android');
Directory get _materializedDirectory => parent.directory.childDirectory('android');
/// True, if the parent Flutter project is a module.
bool get isModule => parent.isModule;
File get gradleManifestFile {
File get appManifestFile {
return isUsingGradle()
? fs.file(fs.path.join(directory.path, 'app', 'src', 'main', 'AndroidManifest.xml'))
: directory.childFile('AndroidManifest.xml');
? fs.file(fs.path.join(hostAppGradleRoot.path, 'app', 'src', 'main', 'AndroidManifest.xml'))
: hostAppGradleRoot.childFile('AndroidManifest.xml');
}
File get gradleAppOutV1File => gradleAppOutV1Directory.childFile('app-debug.apk');
Directory get gradleAppOutV1Directory {
return fs.directory(fs.path.join(directory.path, 'app', 'build', 'outputs', 'apk'));
return fs.directory(fs.path.join(hostAppGradleRoot.path, 'app', 'build', 'outputs', 'apk'));
}
bool isUsingGradle() {
return directory.childFile('build.gradle').existsSync();
return hostAppGradleRoot.childFile('build.gradle').existsSync();
}
Future<String> applicationId() {
final File gradleFile = directory.childDirectory('app').childFile('build.gradle');
final File gradleFile = hostAppGradleRoot.childDirectory('app').childFile('build.gradle');
return _firstMatchInFile(gradleFile, _applicationIdPattern).then((Match match) => match?.group(1));
}
Future<String> group() {
final File gradleFile = directory.childFile('build.gradle');
final File gradleFile = hostAppGradleRoot.childFile('build.gradle');
return _firstMatchInFile(gradleFile, _groupPattern).then((Match match) => match?.group(1));
}
Future<void> ensureReadyForPlatformSpecificTooling() async {
if (isModule && _shouldRegenerateFromTemplate()) {
final Template template = new Template.fromName(fs.path.join('module', 'android'));
template.render(
directory,
<String, dynamic>{
'androidIdentifier': parent.manifest.androidPackage,
},
printStatusWhenWriting: false,
);
gradle.injectGradleWrapper(directory);
_regenerateLibrary();
// Add ephemeral host app, if a materialized host app does not already exist.
if (!_materializedDirectory.existsSync()) {
_overwriteFromTemplate(fs.path.join('module', 'android', 'host_app_common'), _ephemeralDirectory);
_overwriteFromTemplate(fs.path.join('module', 'android', 'host_app_ephemeral'), _ephemeralDirectory);
}
}
if (!directory.existsSync())
if (!hostAppGradleRoot.existsSync()) {
return;
await gradle.updateLocalProperties(project: parent, requireAndroidSdk: false);
}
gradle.updateLocalProperties(project: parent, requireAndroidSdk: false);
}
bool _shouldRegenerateFromTemplate() {
return Cache.instance.fileOlderThanToolsStamp(directory.childFile('build.gradle'));
return Cache.instance.fileOlderThanToolsStamp(_ephemeralDirectory.childFile('build.gradle'));
}
File get localPropertiesFile => directory.childFile('local.properties');
Future<void> materialize() async {
assert(isModule);
if (_materializedDirectory.existsSync())
throwToolExit('Android host app already materialized. To redo materialization, delete the android/ folder.');
_regenerateLibrary();
_overwriteFromTemplate(fs.path.join('module', 'android', 'host_app_common'), _materializedDirectory);
_overwriteFromTemplate(fs.path.join('module', 'android', 'host_app_materialized'), _materializedDirectory);
_overwriteFromTemplate(fs.path.join('module', 'android', 'gradle'), _materializedDirectory);
gradle.injectGradleWrapper(_materializedDirectory);
gradle.writeLocalProperties(_materializedDirectory.childFile('local.properties'));
await injectPlugins(parent);
}
File get localPropertiesFile => _flutterLibGradleRoot.childFile('local.properties');
Directory get pluginRegistrantHost => _flutterLibGradleRoot.childDirectory(isModule ? 'Flutter' : 'app');
void _regenerateLibrary() {
_deleteIfExistsSync(_ephemeralDirectory);
_overwriteFromTemplate(fs.path.join('module', 'android', 'library'), _ephemeralDirectory);
_overwriteFromTemplate(fs.path.join('module', 'android', 'gradle'), _ephemeralDirectory);
gradle.injectGradleWrapper(_ephemeralDirectory);
}
Directory get pluginRegistrantHost => directory.childDirectory(isModule ? 'Flutter' : 'app');
void _deleteIfExistsSync(Directory directory) {
if (directory.existsSync())
directory.deleteSync(recursive: true);
}
void _overwriteFromTemplate(String path, Directory target) {
final Template template = new Template.fromName(path);
template.render(
target,
<String, dynamic>{
'projectName': parent.manifest.appName,
'androidIdentifier': parent.manifest.androidPackage,
},
printStatusWhenWriting: false,
overwriteExisting: true,
);
}
}
/// Asynchronously returns the first line-based match for [regExp] in [file].
......
......@@ -905,7 +905,7 @@ Future<String> getMissingPackageHintForPlatform(TargetPlatform platform) async {
case TargetPlatform.android_x64:
case TargetPlatform.android_x86:
final FlutterProject project = await FlutterProject.current();
final String manifestPath = fs.path.relative(project.android.gradleManifestFile.path);
final String manifestPath = fs.path.relative(project.android.appManifestFile.path);
return 'Is your project missing an $manifestPath?\nConsider running "flutter create ." to create one.';
case TargetPlatform.ios:
return 'Is your project missing an ios/Runner/Info.plist?\nConsider running "flutter create ." to create one.';
......
# Templates for Flutter Module
## common
Written to root of Flutter module.
Adds Dart project files including `pubspec.yaml`.
## android
#### library
Written to the `.android/` hidden folder.
Contents wraps Flutter/Dart code as a Gradle project that defines an
Android library.
Executing `./gradlew flutter:assembleDebug` in that folder produces
a `.aar` archive.
Android host apps can set up a dependency to this project to consume
Flutter views.
#### gradle
Written to `.android/` or `android/`.
Mixin for adding Gradle boilerplate to Android projects. The `build.gradle`
file is a template file so that it is created, not copied, on instantiation.
That way, its timestamp reflects template instantiation time.
#### host_app_common
Written to either `.android/` or `android/`.
Contents define a single-Activity, single-View Android host app
with a dependency on the `.android/Flutter` library.
Executing `./gradlew app:assembleDebug` in the target folder produces
an `.apk` archive.
Used with either `android_host_ephemeral` or `android_host_materialized`.
#### host_app_ephemeral
Written to `.android/` on top of `android_host_common`.
Combined contents define an *ephemeral* (hidden, auto-generated,
under Flutter tooling control) Android host app with a dependency on the
`.android/Flutter` library.
#### host_app_materialized
Written to `android/` on top of `android_host_common`.
Combined contents define a *materialized* (visible, one-time generated,
under app author control) Android host app with a dependency on the
`.android/Flutter` library.
## ios
Written to the `.ios/` hidden folder.
Contents wraps Flutter/Dart code as a CocoaPods pod.
iOS host apps can set up a dependency to this project to consume
Flutter views.
#Fri Jun 23 08:50:38 CEST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
......@@ -7,7 +7,7 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.3'
classpath 'com.android.tools.build:gradle:3.1.4'
}
}
......
......@@ -15,6 +15,7 @@
FlutterApplication and put your custom class here. -->
<application
android:name="io.flutter.app.FlutterApplication"
android:label="{{projectName}}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
......
// Generated file. Do not edit.
include ':app'
setBinding(new Binding([gradle: this]))
evaluate(new File('../.android/include_flutter.groovy'))
// Generated file. Do not edit.
rootProject.name = 'android_generated'
setBinding(new Binding([gradle: this]))
evaluate(new File('include_flutter.groovy'))
......@@ -33,7 +33,7 @@ void main() {
// This test is written to fail if our bots get Android SDKs in the future: shouldBeToolExit
// will be null and our expectation would fail. That would remind us to make these tests
// hermetic before adding Android SDKs to the bots.
await updateLocalProperties(project: await FlutterProject.current());
updateLocalProperties(project: await FlutterProject.current());
} on Exception catch (e) {
shouldBeToolExit = e;
}
......@@ -190,7 +190,7 @@ someOtherProperty: someOtherValue
writeSchemaFile(fs, schemaData);
try {
await updateLocalProperties(
updateLocalProperties(
project: await FlutterProject.fromPath('path/to/project'),
buildInfo: buildInfo,
);
......
......@@ -27,17 +27,6 @@ void main() {
);
});
Future<Null> expectToolExitLater(Future<dynamic> future, Matcher messageMatcher) async {
try {
await future;
fail('ToolExit expected, but nothing thrown');
} on ToolExit catch(e) {
expect(e.message, messageMatcher);
} catch(e, trace) {
fail('ToolExit expected, got $e\n$trace');
}
}
testInMemory('fails on invalid pubspec.yaml', () async {
final Directory directory = fs.directory('myproject');
directory.childFile('pubspec.yaml')
......@@ -95,16 +84,55 @@ void main() {
fs.currentDirectory.absolute.path,
);
});
});
group('materialize Android', () {
testInMemory('fails on non-module', () async {
final FlutterProject project = await someProject();
await expectLater(
project.android.materialize(),
throwsA(const isInstanceOf<AssertionError>()),
);
});
testInMemory('exits on already materialized module', () async {
final FlutterProject project = await aModuleProject();
await project.android.materialize();
expectToolExitLater(project.android.materialize(), contains('already materialized'));
});
testInMemory('creates android/app folder in place of .android/app', () async {
final FlutterProject project = await aModuleProject();
await project.android.materialize();
expectNotExists(project.directory.childDirectory('.android').childDirectory('app'));
expect(
project.directory.childDirectory('.android').childFile('settings.gradle').readAsStringSync(),
isNot(contains("include ':app'")),
);
expectExists(project.directory.childDirectory('android').childDirectory('app'));
expectExists(project.directory.childDirectory('android').childFile('local.properties'));
expect(
project.directory.childDirectory('android').childFile('settings.gradle').readAsStringSync(),
contains("include ':app'"),
);
});
testInMemory('retains .android/Flutter folder and references it', () async {
final FlutterProject project = await aModuleProject();
await project.android.materialize();
expectExists(project.directory.childDirectory('.android').childDirectory('Flutter'));
expect(
project.directory.childDirectory('android').childFile('settings.gradle').readAsStringSync(),
contains('../.android/include_flutter.groovy'),
);
});
testInMemory('can be redone after deletion', () async {
final FlutterProject project = await aModuleProject();
await project.android.materialize();
project.directory.childDirectory('android').deleteSync(recursive: true);
await project.android.materialize();
expectExists(project.directory.childDirectory('android').childDirectory('app'));
});
});
group('ensure ready for platform-specific tooling', () {
void expectExists(FileSystemEntity entity) {
expect(entity.existsSync(), isTrue);
}
void expectNotExists(FileSystemEntity entity) {
expect(entity.existsSync(), isFalse);
}
testInMemory('does nothing, if project is not created', () async {
final FlutterProject project = new FlutterProject(
fs.directory('not_created'),
......@@ -118,9 +146,9 @@ void main() {
final FlutterProject project = await aPluginProject();
await project.ensureReadyForPlatformSpecificTooling();
expectNotExists(project.ios.directory.childDirectory('Runner').childFile('GeneratedPluginRegistrant.h'));
expectNotExists(androidPluginRegistrant(project.android.directory.childDirectory('app')));
expectNotExists(androidPluginRegistrant(project.android.hostAppGradleRoot.childDirectory('app')));
expectNotExists(project.ios.directory.childDirectory('Flutter').childFile('Generated.xcconfig'));
expectNotExists(project.android.directory.childFile('local.properties'));
expectNotExists(project.android.hostAppGradleRoot.childFile('local.properties'));
});
testInMemory('injects plugins for iOS', () async {
final FlutterProject project = await someProject();
......@@ -135,19 +163,19 @@ void main() {
testInMemory('injects plugins for Android', () async {
final FlutterProject project = await someProject();
await project.ensureReadyForPlatformSpecificTooling();
expectExists(androidPluginRegistrant(project.android.directory.childDirectory('app')));
expectExists(androidPluginRegistrant(project.android.hostAppGradleRoot.childDirectory('app')));
});
testInMemory('updates local properties for Android', () async {
final FlutterProject project = await someProject();
await project.ensureReadyForPlatformSpecificTooling();
expectExists(project.android.directory.childFile('local.properties'));
expectExists(project.android.hostAppGradleRoot.childFile('local.properties'));
});
testInMemory('creates Android library in module', () async {
final FlutterProject project = await aModuleProject();
await project.ensureReadyForPlatformSpecificTooling();
expectExists(project.android.directory.childFile('settings.gradle'));
expectExists(project.android.directory.childFile('local.properties'));
expectExists(androidPluginRegistrant(project.android.directory.childDirectory('Flutter')));
expectExists(project.android.hostAppGradleRoot.childFile('settings.gradle'));
expectExists(project.android.hostAppGradleRoot.childFile('local.properties'));
expectExists(androidPluginRegistrant(project.android.hostAppGradleRoot.childDirectory('Flutter')));
});
testInMemory('creates iOS pod in module', () async {
final FlutterProject project = await aModuleProject();
......@@ -169,7 +197,7 @@ void main() {
expect(project.isModule, isTrue);
expect(project.android.isModule, isTrue);
expect(project.ios.isModule, isTrue);
expect(project.android.directory.basename, '.android');
expect(project.android.hostAppGradleRoot.basename, '.android');
expect(project.ios.directory.basename, '.ios');
});
testInMemory('is known for non-module', () async {
......@@ -177,7 +205,7 @@ void main() {
expect(project.isModule, isFalse);
expect(project.android.isModule, isFalse);
expect(project.ios.isModule, isFalse);
expect(project.android.directory.basename, 'android');
expect(project.android.hostAppGradleRoot.basename, 'android');
expect(project.ios.directory.basename, 'ios');
});
});
......@@ -326,6 +354,25 @@ void transfer(FileSystemEntity entity, FileSystem target) {
}
}
Future<Null> expectToolExitLater(Future<dynamic> future, Matcher messageMatcher) async {
try {
await future;
fail('ToolExit expected, but nothing thrown');
} on ToolExit catch(e) {
expect(e.message, messageMatcher);
} catch(e, trace) {
fail('ToolExit expected, got $e\n$trace');
}
}
void expectExists(FileSystemEntity entity) {
expect(entity.existsSync(), isTrue);
}
void expectNotExists(FileSystemEntity entity) {
expect(entity.existsSync(), isFalse);
}
void addIosWithBundleId(Directory directory, String id) {
directory
.childDirectory('ios')
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment