From 3c83c52697fb478f205cf5d3069324caaf640604 Mon Sep 17 00:00:00 2001 From: Mikkel Nygaard Ravn Date: Tue, 7 Aug 2018 23:43:15 +0200 Subject: [PATCH] FlutterProject refactoring and test coverage (#20296) --- .../flutter_tools/lib/src/android/gradle.dart | 12 +- .../flutter_tools/lib/src/ios/xcodeproj.dart | 22 +- packages/flutter_tools/lib/src/plugins.dart | 6 +- packages/flutter_tools/lib/src/project.dart | 175 ++++++++-------- packages/flutter_tools/test/project_test.dart | 194 +++++++++++++++--- 5 files changed, 266 insertions(+), 143 deletions(-) diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart index 81849e16acc..0ef82d04628 100644 --- a/packages/flutter_tools/lib/src/android/gradle.dart +++ b/packages/flutter_tools/lib/src/android/gradle.dart @@ -200,22 +200,26 @@ distributionUrl=https\\://services.gradle.org/distributions/gradle-$gradleVersio } } -/// Overwrite android/local.properties in the specified Flutter project, if needed. +/// Overwrite local.properties in the specified Flutter project's Android +/// sub-project, if needed. /// -/// Throws, if `pubspec.yaml` or Android SDK cannot be located. +/// Throws tool exit, if `pubspec.yaml` is invalid. /// -/// If [requireSdk] is `true` this will fail with a tool-exit if no Android Sdk +/// If [requireSdk] is `true` this will fail with a tool exit if no Android Sdk /// is found. Future updateLocalProperties({ @required FlutterProject project, BuildInfo buildInfo, bool requireAndroidSdk = true, }) async { + if (project.manifest == null) { + throwToolExit('Invalid `pubspec.yaml`'); + } if (requireAndroidSdk && androidSdk == null) { throwToolExit('Unable to locate Android SDK. Please run `flutter doctor` for more details.'); } - final File localProperties = project.androidLocalPropertiesFile; + final File localProperties = project.android.localPropertiesFile; bool changed = false; SettingsFile settings; diff --git a/packages/flutter_tools/lib/src/ios/xcodeproj.dart b/packages/flutter_tools/lib/src/ios/xcodeproj.dart index 34298105147..18b629b6e03 100644 --- a/packages/flutter_tools/lib/src/ios/xcodeproj.dart +++ b/packages/flutter_tools/lib/src/ios/xcodeproj.dart @@ -15,7 +15,6 @@ import '../base/process.dart'; import '../base/process_manager.dart'; import '../base/utils.dart'; import '../build_info.dart'; -import '../bundle.dart' as bundle; import '../cache.dart'; import '../flutter_manifest.dart'; import '../globals.dart'; @@ -28,25 +27,6 @@ String flutterFrameworkDir(BuildMode mode) { return fs.path.normalize(fs.path.dirname(artifacts.getArtifactPath(Artifact.flutterFramework, TargetPlatform.ios, mode))); } -/// Writes default Xcode properties files in the Flutter project at [projectPath], -/// if project is an iOS project and such files are out of date or do not -/// already exist. -Future generateXcodeProperties({FlutterProject project}) async { - if (project.manifest.isModule || - project.ios.directory.existsSync()) { - if (!Cache.instance.fileOlderThanToolsStamp(project.generatedXcodePropertiesFile)) { - return; - } - - await updateGeneratedXcodeProperties( - project: project, - buildInfo: BuildInfo.debug, - targetOverride: bundle.defaultMainPath, - previewDart2: true, - ); - } -} - /// Writes or rewrites Xcode property files with the specified information. /// /// targetOverride: Optional parameter, if null or unspecified the default value @@ -119,7 +99,7 @@ Future updateGeneratedXcodeProperties({ localsBuffer.writeln('TRACK_WIDGET_CREATION=true'); } - final File generatedXcodePropertiesFile = project.generatedXcodePropertiesFile; + final File generatedXcodePropertiesFile = project.ios.generatedXcodePropertiesFile; generatedXcodePropertiesFile.createSync(recursive: true); generatedXcodePropertiesFile.writeAsStringSync(localsBuffer.toString()); } diff --git a/packages/flutter_tools/lib/src/plugins.dart b/packages/flutter_tools/lib/src/plugins.dart index 93590d24b8e..8943720614b 100644 --- a/packages/flutter_tools/lib/src/plugins.dart +++ b/packages/flutter_tools/lib/src/plugins.dart @@ -158,7 +158,7 @@ Future _writeAndroidPluginRegistrant(FlutterProject project, List }; final String javaSourcePath = fs.path.join( - project.androidPluginRegistrantHost.path, + project.android.pluginRegistrantHost.path, 'src', 'main', 'java', @@ -247,8 +247,8 @@ Future _writeIOSPluginRegistrant(FlutterProject project, List plug 'plugins': iosPlugins, }; - final String registryDirectory = project.iosPluginRegistrantHost.path; - if (project.manifest.isModule) { + final String registryDirectory = project.ios.pluginRegistrantHost.path; + if (project.isModule) { final String registryClassesDirectory = fs.path.join(registryDirectory, 'Classes'); _renderTemplateToFile( _iosPluginRegistrantPodspecTemplate, diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart index 196924c13af..b10192c182a 100644 --- a/packages/flutter_tools/lib/src/project.dart +++ b/packages/flutter_tools/lib/src/project.dart @@ -5,8 +5,11 @@ import 'dart:async'; import 'dart:convert'; +import 'package:meta/meta.dart'; + import 'android/gradle.dart' as gradle; import 'base/file_system.dart'; +import 'build_info.dart'; import 'bundle.dart' as bundle; import 'cache.dart'; import 'flutter_manifest.dart'; @@ -16,21 +19,27 @@ import 'template.dart'; /// Represents the contents of a Flutter project at the specified [directory]. /// -/// Instances should be treated as immutable snapshots, to be replaced by new -/// instances on changes to `pubspec.yaml` files. +/// [FlutterManifest] information is read from `pubspec.yaml` and +/// `example/pubspec.yaml` files on construction of a [FlutterProject] instance. +/// The constructed instance carries an immutable snapshot representation of the +/// presence and content of those files. Accordingly, [FlutterProject] instances +/// should be discarded upon changes to the `pubspec.yaml` files, but can be +/// used across changes to other files, as no other file-level information is +/// cached. class FlutterProject { - FlutterProject._(this.directory, this.manifest, this._exampleManifest); + @visibleForTesting + FlutterProject(this.directory, this.manifest, this._exampleManifest); /// Returns a future that completes with a FlutterProject view of the given directory. static Future fromDirectory(Directory directory) async { final FlutterManifest manifest = await FlutterManifest.createFromPath( directory.childFile(bundle.defaultManifestPath).path, ); - final Directory exampleDirectory = directory.childDirectory('example'); + final Directory exampleDirectory = _exampleDirectory(directory); final FlutterManifest exampleManifest = await FlutterManifest.createFromPath( exampleDirectory.childFile(bundle.defaultManifestPath).path, ); - return new FlutterProject._(directory, manifest, exampleManifest); + return new FlutterProject(directory, manifest, exampleManifest); } /// Returns a future that completes with a FlutterProject view of the current directory. @@ -73,83 +82,56 @@ class FlutterProject { } /// The iOS sub project of this project. - IosProject get ios => new IosProject(directory.childDirectory('ios')); + IosProject get ios => new IosProject._(this); /// The Android sub project of this project. - AndroidProject get android { - if (manifest.isModule) { - return new AndroidProject(directory.childDirectory('.android')); - } - return new AndroidProject(directory.childDirectory('android')); - } - - /// The generated AndroidModule sub project of this module project. - AndroidModuleProject get androidModule => new AndroidModuleProject(directory.childDirectory('.android')); - - /// The generated IosModule sub project of this module project. - IosModuleProject get iosModule => new IosModuleProject(directory.childDirectory('.ios')); - - File get androidLocalPropertiesFile { - return directory - .childDirectory(manifest.isModule ? '.android' : 'android') - .childFile('local.properties'); - } - - File get generatedXcodePropertiesFile { - return directory - .childDirectory(manifest.isModule ? '.ios' : 'ios') - .childDirectory('Flutter') - .childFile('Generated.xcconfig'); - } + AndroidProject get android => new AndroidProject._(this); File get flutterPluginsFile => directory.childFile('.flutter-plugins'); - Directory get androidPluginRegistrantHost { - return manifest.isModule - ? directory.childDirectory('.android').childDirectory('Flutter') - : directory.childDirectory('android').childDirectory('app'); - } - - Directory get iosPluginRegistrantHost { - // In a module create the GeneratedPluginRegistrant as a pod to be included - // from a hosting app. - // For a non-module create the GeneratedPluginRegistrant as source files - // directly in the iOS project. - return manifest.isModule - ? directory.childDirectory('.ios').childDirectory('Flutter').childDirectory('FlutterPluginRegistrant') - : directory.childDirectory('ios').childDirectory('Runner'); - } - /// The example sub-project of this project. - FlutterProject get example => new FlutterProject._(_exampleDirectory, _exampleManifest, FlutterManifest.empty()); + FlutterProject get example => new FlutterProject( + _exampleDirectory(directory), + _exampleManifest, + FlutterManifest.empty(), + ); - /// True, if this project has an example application - bool get hasExampleApp => _exampleDirectory.childFile('pubspec.yaml').existsSync(); + bool get isModule => manifest != null && manifest.isModule; + + /// True, if this project has an example application. + bool get hasExampleApp => _exampleDirectory(directory).existsSync(); /// The directory that will contain the example if an example exists. - Directory get _exampleDirectory => directory.childDirectory('example'); + static Directory _exampleDirectory(Directory directory) => directory.childDirectory('example'); /// Generates project files necessary to make Gradle builds work on Android /// and CocoaPods+Xcode work on iOS, for app and module projects only. Future ensureReadyForPlatformSpecificTooling() async { - if (!directory.existsSync() || hasExampleApp) { + if (!directory.existsSync() || hasExampleApp) return; - } - if (manifest.isModule) { - await androidModule.ensureReadyForPlatformSpecificTooling(this); - await iosModule.ensureReadyForPlatformSpecificTooling(); - } - await xcode.generateXcodeProperties(project: this); + await android.ensureReadyForPlatformSpecificTooling(); + await ios.ensureReadyForPlatformSpecificTooling(); await injectPlugins(this); } } -/// Represents the contents of the ios/ folder of a Flutter project. +/// Represents the iOS sub-project of a Flutter project. +/// +/// Instances will reflect the contents of the `ios/` sub-folder of +/// Flutter applications and the `.ios/` sub-folder of Flutter modules. class IosProject { static final RegExp _productBundleIdPattern = new RegExp(r'^\s*PRODUCT_BUNDLE_IDENTIFIER\s*=\s*(.*);\s*$'); - IosProject(this.directory); - final Directory directory; + IosProject._(this.parent); + + /// The parent of this project. + final FlutterProject parent; + + /// The directory of this project. + Directory get directory => parent.directory.childDirectory(isModule ? '.ios' : 'ios'); + + /// True, if the parent Flutter project is a module. + bool get isModule => parent.isModule; /// The xcode config file for [mode]. File xcodeConfigFor(String mode) => directory.childDirectory('Flutter').childFile('$mode.xcconfig'); @@ -167,33 +149,55 @@ class IosProject { final File projectFile = directory.childDirectory('Runner.xcodeproj').childFile('project.pbxproj'); return _firstMatchInFile(projectFile, _productBundleIdPattern).then((Match match) => match?.group(1)); } -} - -/// Represents the contents of the .ios/ folder of a Flutter module -/// project. -class IosModuleProject { - IosModuleProject(this.directory); - - final Directory directory; Future ensureReadyForPlatformSpecificTooling() async { - if (_shouldRegenerate()) { + if (isModule && _shouldRegenerateFromTemplate()) { final Template template = new Template.fromName(fs.path.join('module', 'ios')); template.render(directory, {}, printStatusWhenWriting: false); } + if (!directory.existsSync()) + return; + if (Cache.instance.fileOlderThanToolsStamp(generatedXcodePropertiesFile)) { + await xcode.updateGeneratedXcodeProperties( + project: parent, + buildInfo: BuildInfo.debug, + targetOverride: bundle.defaultMainPath, + previewDart2: true, + ); + } } - bool _shouldRegenerate() { + bool _shouldRegenerateFromTemplate() { return Cache.instance.fileOlderThanToolsStamp(directory.childFile('podhelper.rb')); } + + File get generatedXcodePropertiesFile => directory.childDirectory('Flutter').childFile('Generated.xcconfig'); + + Directory get pluginRegistrantHost { + return isModule + ? directory.childDirectory('Flutter').childDirectory('FlutterPluginRegistrant') + : directory.childDirectory('Runner'); + } } -/// Represents the contents of the android/ folder of a Flutter project. +/// Represents the Android sub-project of a Flutter project. +/// +/// Instances will reflect the contents of the `android/` sub-folder of +/// Flutter applications and the `.android/` sub-folder of Flutter modules. class AndroidProject { static final RegExp _applicationIdPattern = new RegExp('^\\s*applicationId\\s+[\'\"](.*)[\'\"]\\s*\$'); static final RegExp _groupPattern = new RegExp('^\\s*group\\s+[\'\"](.*)[\'\"]\\s*\$'); - AndroidProject(this.directory); + AndroidProject._(this.parent); + + /// The parent of this project. + final FlutterProject parent; + + /// The directory of this project. + Directory get directory => parent.directory.childDirectory(isModule ? '.android' : 'android'); + + /// True, if the parent Flutter project is a module. + bool get isModule => parent.isModule; File get gradleManifestFile { return isUsingGradle() @@ -211,8 +215,6 @@ class AndroidProject { return directory.childFile('build.gradle').existsSync(); } - final Directory directory; - Future applicationId() { final File gradleFile = directory.childDirectory('app').childFile('build.gradle'); return _firstMatchInFile(gradleFile, _applicationIdPattern).then((Match match) => match?.group(1)); @@ -222,32 +224,31 @@ class AndroidProject { final File gradleFile = directory.childFile('build.gradle'); return _firstMatchInFile(gradleFile, _groupPattern).then((Match match) => match?.group(1)); } -} -/// Represents the contents of the .android/ folder of a Flutter module project. -class AndroidModuleProject { - AndroidModuleProject(this.directory); - - final Directory directory; - - Future ensureReadyForPlatformSpecificTooling(FlutterProject project) async { - if (_shouldRegenerate()) { + Future ensureReadyForPlatformSpecificTooling() async { + if (isModule && _shouldRegenerateFromTemplate()) { final Template template = new Template.fromName(fs.path.join('module', 'android')); template.render( directory, { - 'androidIdentifier': project.manifest.androidPackage, + 'androidIdentifier': parent.manifest.androidPackage, }, printStatusWhenWriting: false, ); gradle.injectGradleWrapper(directory); } - await gradle.updateLocalProperties(project: project, requireAndroidSdk: false); + if (!directory.existsSync()) + return; + await gradle.updateLocalProperties(project: parent, requireAndroidSdk: false); } - bool _shouldRegenerate() { + bool _shouldRegenerateFromTemplate() { return Cache.instance.fileOlderThanToolsStamp(directory.childFile('build.gradle')); } + + File get localPropertiesFile => directory.childFile('local.properties'); + + Directory get pluginRegistrantHost => directory.childDirectory(isModule ? 'Flutter' : 'app'); } /// Asynchronously returns the first line-based match for [regExp] in [file]. diff --git a/packages/flutter_tools/test/project_test.dart b/packages/flutter_tools/test/project_test.dart index e5db386fe85..eaf56ea0c25 100644 --- a/packages/flutter_tools/test/project_test.dart +++ b/packages/flutter_tools/test/project_test.dart @@ -3,12 +3,14 @@ // found in the LICENSE file. import 'dart:async'; +import 'package:flutter_tools/src/base/context.dart'; +import 'package:flutter_tools/src/cache.dart'; +import 'package:flutter_tools/src/flutter_manifest.dart'; import 'package:flutter_tools/src/project.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:test/test.dart'; import 'package:file/file.dart'; import 'package:file/memory.dart'; - import 'src/context.dart'; void main() { @@ -23,73 +25,151 @@ void main() { (await FlutterProject.fromPath(directory.path)).directory.absolute.path, directory.absolute.path, ); + expect( + (await FlutterProject.current()).directory.absolute.path, + fs.currentDirectory.absolute.path, + ); }); + group('ensure ready for platform-specific tooling', () { testInMemory('does nothing, if project is not created', () async { - final FlutterProject project = await someProject(); + final FlutterProject project = new FlutterProject( + fs.directory('not_created'), + FlutterManifest.empty(), + FlutterManifest.empty(), + ); await project.ensureReadyForPlatformSpecificTooling(); expect(project.directory.existsSync(), isFalse); }); testInMemory('does nothing in plugin or package root project', () async { - final FlutterProject project = await aPluginProject(); + final FlutterProject project = aPluginProject(); await project.ensureReadyForPlatformSpecificTooling(); expect(project.ios.directory.childFile('Runner/GeneratedPluginRegistrant.h').existsSync(), isFalse); + expect(project.android.directory.childFile( + 'app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java', + ).existsSync(), isFalse); expect(project.ios.directory.childFile('Flutter/Generated.xcconfig').existsSync(), isFalse); + expect(project.android.directory.childFile('local.properties').existsSync(), isFalse); }); - testInMemory('injects plugins', () async { - final FlutterProject project = await aProjectWithIos(); + testInMemory('injects plugins for iOS', () async { + final FlutterProject project = someProject(); await project.ensureReadyForPlatformSpecificTooling(); expect(project.ios.directory.childFile('Runner/GeneratedPluginRegistrant.h').existsSync(), isTrue); }); - testInMemory('generates Xcode configuration', () async { - final FlutterProject project = await aProjectWithIos(); + testInMemory('generates Xcode configuration for iOS', () async { + final FlutterProject project = someProject(); await project.ensureReadyForPlatformSpecificTooling(); expect(project.ios.directory.childFile('Flutter/Generated.xcconfig').existsSync(), isTrue); }); + testInMemory('injects plugins for Android', () async { + final FlutterProject project = someProject(); + await project.ensureReadyForPlatformSpecificTooling(); + expect(project.android.directory.childFile( + 'app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java', + ).existsSync(), isTrue); + }); + testInMemory('updates local properties for Android', () async { + final FlutterProject project = someProject(); + await project.ensureReadyForPlatformSpecificTooling(); + expect(project.android.directory.childFile('local.properties').existsSync(), isTrue); + }); + testInMemory('creates Android library in module', () async { + final FlutterProject project = aModuleProject(); + await project.ensureReadyForPlatformSpecificTooling(); + expect(project.android.directory.childFile('template_content').existsSync(), isTrue); + expect(project.android.directory.childFile('local.properties').existsSync(), isTrue); + expect(project.android.directory.childFile( + 'Flutter/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java', + ).existsSync(), isTrue); + }); + testInMemory('creates iOS pod in module', () async { + final FlutterProject project = aModuleProject(); + await project.ensureReadyForPlatformSpecificTooling(); + final Directory flutter = project.ios.directory.childDirectory('Flutter'); + expect(flutter.childFile('template_content').existsSync(), isTrue); + expect(flutter.childFile('Generated.xcconfig').existsSync(), isTrue); + expect(flutter.childFile( + 'FlutterPluginRegistrant/Classes/GeneratedPluginRegistrant.h', + ).existsSync(), isTrue); + expect(flutter.childFile( + 'FlutterPluginRegistrant/Classes/GeneratedPluginRegistrant.m', + ).existsSync(), isTrue); + }); }); + + group('module status', () { + testInMemory('is known for module', () async { + final FlutterProject project = aModuleProject(); + expect(project.isModule, isTrue); + expect(project.android.isModule, isTrue); + expect(project.ios.isModule, isTrue); + expect(project.android.directory.path, startsWith('module_project/.android')); + expect(project.ios.directory.path, startsWith('module_project/.ios')); + }); + testInMemory('is known for non-module', () async { + final FlutterProject project = someProject(); + expect(project.isModule, isFalse); + expect(project.android.isModule, isFalse); + expect(project.ios.isModule, isFalse); + expect(project.android.directory.path, startsWith('some_project/android')); + expect(project.ios.directory.path, startsWith('some_project/ios')); + }); + }); + + group('example', () { + testInMemory('exists for plugin', () async { + final FlutterProject project = aPluginProject(); + expect(project.hasExampleApp, isTrue); + }); + testInMemory('does not exist for non-plugin', () async { + final FlutterProject project = someProject(); + expect(project.hasExampleApp, isFalse); + }); + }); + group('organization names set', () { testInMemory('is empty, if project not created', () async { - final FlutterProject project = await someProject(); + final FlutterProject project = someProject(); expect(await project.organizationNames(), isEmpty); }); testInMemory('is empty, if no platform folders exist', () async { - final FlutterProject project = await someProject(); + final FlutterProject project = someProject(); project.directory.createSync(); expect(await project.organizationNames(), isEmpty); }); testInMemory('is populated from iOS bundle identifier', () async { - final FlutterProject project = await someProject(); + final FlutterProject project = someProject(); addIosWithBundleId(project.directory, 'io.flutter.someProject'); expect(await project.organizationNames(), ['io.flutter']); }); testInMemory('is populated from Android application ID', () async { - final FlutterProject project = await someProject(); + final FlutterProject project = someProject(); addAndroidWithApplicationId(project.directory, 'io.flutter.someproject'); expect(await project.organizationNames(), ['io.flutter']); }); testInMemory('is populated from iOS bundle identifier in plugin example', () async { - final FlutterProject project = await someProject(); + final FlutterProject project = someProject(); addIosWithBundleId(project.example.directory, 'io.flutter.someProject'); expect(await project.organizationNames(), ['io.flutter']); }); testInMemory('is populated from Android application ID in plugin example', () async { - final FlutterProject project = await someProject(); + final FlutterProject project = someProject(); addAndroidWithApplicationId(project.example.directory, 'io.flutter.someproject'); expect(await project.organizationNames(), ['io.flutter']); }); testInMemory('is populated from Android group in plugin', () async { - final FlutterProject project = await someProject(); + final FlutterProject project = someProject(); addAndroidWithGroup(project.directory, 'io.flutter.someproject'); expect(await project.organizationNames(), ['io.flutter']); }); testInMemory('is singleton, if sources agree', () async { - final FlutterProject project = await someProject(); + final FlutterProject project = someProject(); addIosWithBundleId(project.directory, 'io.flutter.someProject'); addAndroidWithApplicationId(project.directory, 'io.flutter.someproject'); expect(await project.organizationNames(), ['io.flutter']); }); testInMemory('is non-singleton, if sources disagree', () async { - final FlutterProject project = await someProject(); + final FlutterProject project = someProject(); addIosWithBundleId(project.directory, 'io.flutter.someProject'); addAndroidWithApplicationId(project.directory, 'io.clutter.someproject'); expect( @@ -101,30 +181,88 @@ void main() { }); } -Future someProject() => FlutterProject.fromPath('some_project'); - -Future aProjectWithIos() { - final Directory directory = fs.directory('ios_project'); - directory.childFile('pubspec.yaml').createSync(recursive: true); +FlutterProject someProject() { + final Directory directory = fs.directory('some_project'); directory.childFile('.packages').createSync(recursive: true); directory.childDirectory('ios').createSync(recursive: true); - return FlutterProject.fromDirectory(directory); + directory.childDirectory('android').createSync(recursive: true); + return new FlutterProject( + directory, + FlutterManifest.empty(), + FlutterManifest.empty(), + ); } -Future aPluginProject() { - final Directory directory = fs.directory('plugin_project/example'); - directory.childFile('pubspec.yaml').createSync(recursive: true); - directory.childFile('.packages').createSync(recursive: true); +FlutterProject aPluginProject() { + final Directory directory = fs.directory('plugin_project'); directory.childDirectory('ios').createSync(recursive: true); - return FlutterProject.fromDirectory(directory.parent); + directory.childDirectory('android').createSync(recursive: true); + directory.childDirectory('example').createSync(recursive: true); + return new FlutterProject( + directory, + FlutterManifest.mock(const { + 'flutter': { + 'plugin': {} + } + }), + FlutterManifest.empty(), + ); +} + +FlutterProject aModuleProject() { + final Directory directory = fs.directory('module_project'); + directory.childFile('.packages').createSync(recursive: true); + return new FlutterProject( + directory, + FlutterManifest.mock(const { + 'flutter': { + 'module': { + 'androidPackage': 'com.example' + } + } + }), + FlutterManifest.empty(), + ); } void testInMemory(String description, Future testMethod()) { + Cache.flutterRoot = 'flutter'; + final FileSystem fs = new MemoryFileSystem(); + // Pretend we have a Flutter module project template. + fs.directory(Cache.flutterRoot) + .childDirectory('packages') + .childDirectory('flutter_tools') + .childDirectory('templates') + .childDirectory('module') + .childDirectory('android') + .childFile('template_content.copy.tmpl') + .createSync(recursive: true); + fs.directory(Cache.flutterRoot) + .childDirectory('packages') + .childDirectory('flutter_tools') + .childDirectory('templates') + .childDirectory('module') + .childDirectory('ios') + .childDirectory('Flutter.tmpl') + .childFile('template_content.copy.tmpl') + .createSync(recursive: true); + + // Sets up cache in a test execution context where `fs` is the file system. + Cache cacheCreator() { + final Cache cache = new Cache(rootOverride: fs.directory('flutter')); + cache.getArtifactDirectory('gradle_wrapper') + .childDirectory('gradle') + .childDirectory('wrapper') + .childFile('gradle-wrapper.properties') + .createSync(recursive: true); + return cache; + } testUsingContext( description, testMethod, overrides: { - FileSystem: () => new MemoryFileSystem(), + FileSystem: () => fs, + Cache: cacheCreator, }, ); }