Move plugin injection to just after pub get (#14743)
diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart
index e0d5149..75af21b 100644
--- a/packages/flutter_tools/lib/src/android/gradle.dart
+++ b/packages/flutter_tools/lib/src/android/gradle.dart
@@ -16,7 +16,6 @@
import '../build_info.dart';
import '../cache.dart';
import '../globals.dart';
-import '../plugins.dart';
import 'android_sdk.dart';
import 'android_studio.dart';
@@ -94,7 +93,6 @@
Future<GradleProject> _readGradleProject() async {
final String gradle = await _ensureGradle();
updateLocalProperties();
- injectPlugins();
try {
final Status status = logger.startProgress('Resolving dependencies...', expectSlowOperation: true);
final RunResult runResult = await runCheckedAsync(
diff --git a/packages/flutter_tools/lib/src/application_package.dart b/packages/flutter_tools/lib/src/application_package.dart
index f98dd46a..ea4e9dc3 100644
--- a/packages/flutter_tools/lib/src/application_package.dart
+++ b/packages/flutter_tools/lib/src/application_package.dart
@@ -181,11 +181,11 @@
if (id == null)
return null;
final String projectPath = fs.path.join('ios', 'Runner.xcodeproj');
- final Map<String, String> buildSettings = getXcodeBuildSettings(projectPath, 'Runner');
+ final Map<String, String> buildSettings = xcodeProjectInterpreter.getBuildSettings(projectPath, 'Runner');
id = substituteXcodeVariables(id, buildSettings);
return new BuildableIOSApp(
- appDirectory: fs.path.join('ios'),
+ appDirectory: 'ios',
projectBundleId: id,
buildSettings: buildSettings,
);
diff --git a/packages/flutter_tools/lib/src/commands/create.dart b/packages/flutter_tools/lib/src/commands/create.dart
index 643a452..5956021 100644
--- a/packages/flutter_tools/lib/src/commands/create.dart
+++ b/packages/flutter_tools/lib/src/commands/create.dart
@@ -14,14 +14,10 @@
import '../base/file_system.dart';
import '../base/os.dart';
import '../base/utils.dart';
-import '../build_info.dart';
import '../cache.dart';
import '../dart/pub.dart';
import '../doctor.dart';
-import '../flx.dart' as flx;
import '../globals.dart';
-import '../ios/xcodeproj.dart';
-import '../plugins.dart';
import '../project.dart';
import '../runner/flutter_command.dart';
import '../template.dart';
@@ -232,17 +228,9 @@
printStatus('Wrote $generatedCount files.');
printStatus('');
- updateXcodeGeneratedProperties(
- projectPath: appPath,
- buildInfo: BuildInfo.debug,
- target: flx.defaultMainPath,
- hasPlugins: generatePlugin,
- previewDart2: false,
- );
-
if (argResults['pub']) {
await pubGet(context: PubContext.create, directory: appPath, offline: argResults['offline']);
- injectPlugins(directory: appPath);
+ new FlutterProject(fs.directory(appPath)).ensureReadyForPlatformSpecificTooling();
}
if (android_sdk.androidSdk != null)
diff --git a/packages/flutter_tools/lib/src/commands/inject_plugins.dart b/packages/flutter_tools/lib/src/commands/inject_plugins.dart
index f1df432..6886937 100644
--- a/packages/flutter_tools/lib/src/commands/inject_plugins.dart
+++ b/packages/flutter_tools/lib/src/commands/inject_plugins.dart
@@ -24,7 +24,8 @@
@override
Future<Null> runCommand() async {
- final bool result = injectPlugins().hasPlugin;
+ injectPlugins();
+ final bool result = hasPlugins();
if (result) {
printStatus('GeneratedPluginRegistrants successfully written.');
} else {
diff --git a/packages/flutter_tools/lib/src/commands/packages.dart b/packages/flutter_tools/lib/src/commands/packages.dart
index 44aafbc..3600289 100644
--- a/packages/flutter_tools/lib/src/commands/packages.dart
+++ b/packages/flutter_tools/lib/src/commands/packages.dart
@@ -5,8 +5,10 @@
import 'dart:async';
import '../base/common.dart';
+import '../base/file_system.dart';
import '../base/os.dart';
import '../dart/pub.dart';
+import '../project.dart';
import '../runner/flutter_command.dart';
class PackagesCommand extends FlutterCommand {
@@ -75,6 +77,7 @@
offline: argResults['offline'],
checkLastModified: false,
);
+ new FlutterProject(fs.directory(target)).ensureReadyForPlatformSpecificTooling();
}
}
diff --git a/packages/flutter_tools/lib/src/ios/cocoapods.dart b/packages/flutter_tools/lib/src/ios/cocoapods.dart
index da63cdc..1d558d4 100644
--- a/packages/flutter_tools/lib/src/ios/cocoapods.dart
+++ b/packages/flutter_tools/lib/src/ios/cocoapods.dart
@@ -16,6 +16,7 @@
import '../base/version.dart';
import '../cache.dart';
import '../globals.dart';
+import 'xcodeproj.dart';
const String noCocoaPodsConsequence = '''
CocoaPods is used to retrieve the iOS platform side's plugin code that responds to your plugin usage on the Dart side.
@@ -60,13 +61,13 @@
// For backward compatibility with previously created Podfile only.
@required String iosEngineDir,
bool isSwift: false,
- bool pluginOrFlutterPodChanged: true,
+ bool flutterPodChanged: true,
}) async {
+ if (!(await appIosDir.childFile('Podfile').exists())) {
+ throwToolExit('Podfile missing');
+ }
if (await _checkPodCondition()) {
- if (!fs.file(fs.path.join(appIosDir.path, 'Podfile')).existsSync()) {
- await _createPodfile(appIosDir, isSwift);
- } // TODO(xster): Add more logic for handling merge conflicts.
- if (_shouldRunPodInstall(appIosDir.path, pluginOrFlutterPodChanged))
+ if (_shouldRunPodInstall(appIosDir.path, flutterPodChanged))
await _runPodInstall(appIosDir, iosEngineDir);
}
}
@@ -99,39 +100,69 @@
return true;
}
- Future<Null> _createPodfile(Directory bundle, bool isSwift) async {
- final File podfileTemplate = fs.file(fs.path.join(
- Cache.flutterRoot,
- 'packages',
- 'flutter_tools',
- 'templates',
- 'cocoapods',
- isSwift ? 'Podfile-swift' : 'Podfile-objc',
- ));
- podfileTemplate.copySync(fs.path.join(bundle.path, 'Podfile'));
+ /// Ensures the `ios` sub-project of the Flutter project at [directory]
+ /// contains a suitable `Podfile` and that its `Flutter/Xxx.xcconfig` files
+ /// include pods configuration.
+ void setupPodfile(String directory) {
+ if (!xcodeProjectInterpreter.canInterpretXcodeProjects) {
+ // Don't do anything for iOS when host platform doesn't support it.
+ return;
+ }
+ final String podfilePath = fs.path.join(directory, 'ios', 'Podfile');
+ if (!fs.file(podfilePath).existsSync()) {
+ final bool isSwift = xcodeProjectInterpreter.getBuildSettings(
+ fs.path.join(directory, 'ios', 'Runner.xcodeproj'),
+ 'Runner',
+ ).containsKey('SWIFT_VERSION');
+ final File podfileTemplate = fs.file(fs.path.join(
+ Cache.flutterRoot,
+ 'packages',
+ 'flutter_tools',
+ 'templates',
+ 'cocoapods',
+ isSwift ? 'Podfile-swift' : 'Podfile-objc',
+ ));
+ podfileTemplate.copySync(podfilePath);
+ }
+ _addPodsDependencyToFlutterXcconfig(directory, 'Debug');
+ _addPodsDependencyToFlutterXcconfig(directory, 'Release');
+ }
+
+ void _addPodsDependencyToFlutterXcconfig(String directory, String mode) {
+ final File file = fs.file(fs.path.join(directory, 'ios', 'Flutter', '$mode.xcconfig'));
+ if (file.existsSync()) {
+ final String content = file.readAsStringSync();
+ final String include = '#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.${mode
+ .toLowerCase()}.xcconfig"';
+ if (!content.contains(include))
+ file.writeAsStringSync('$include\n$content', flush: true);
+ }
+ }
+
+ /// Ensures that pod install is deemed needed on next check.
+ void invalidatePodInstallOutput(String directory) {
+ final File manifest = fs.file(
+ fs.path.join(directory, 'ios', 'Pods', 'Manifest.lock'),
+ );
+ if (manifest.existsSync())
+ manifest.deleteSync();
}
// Check if you need to run pod install.
// The pod install will run if any of below is true.
- // 1. Any plugins changed (add/update/delete)
- // 2. The flutter.framework has changed (debug/release/profile)
- // 3. The podfile.lock doesn't exists
- // 4. The Pods/manifest.lock doesn't exists
- // 5. The podfile.lock doesn't match Pods/manifest.lock.
- bool _shouldRunPodInstall(String appDir, bool pluginOrFlutterPodChanged) {
- if (pluginOrFlutterPodChanged)
+ // 1. The flutter.framework has changed (debug/release/profile)
+ // 2. The podfile.lock doesn't exist
+ // 3. The Pods/Manifest.lock doesn't exist (It is deleted when plugins change)
+ // 4. The podfile.lock doesn't match Pods/Manifest.lock.
+ bool _shouldRunPodInstall(String appDir, bool flutterPodChanged) {
+ if (flutterPodChanged)
return true;
- // Check if podfile.lock and Pods/Manifest.lock exists and matches.
+ // Check if podfile.lock and Pods/Manifest.lock exist and match.
final File podfileLockFile = fs.file(fs.path.join(appDir, 'Podfile.lock'));
- final File manifestLockFile =
- fs.file(fs.path.join(appDir, 'Pods', 'Manifest.lock'));
- if (!podfileLockFile.existsSync()
+ final File manifestLockFile = fs.file(fs.path.join(appDir, 'Pods', 'Manifest.lock'));
+ return !podfileLockFile.existsSync()
|| !manifestLockFile.existsSync()
- || podfileLockFile.readAsStringSync() !=
- manifestLockFile.readAsStringSync()) {
- return true;
- }
- return false;
+ || podfileLockFile.readAsStringSync() != manifestLockFile.readAsStringSync();
}
Future<Null> _runPodInstall(Directory bundle, String engineDirectory) async {
diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart
index 32d37b1..b2a5f6f 100644
--- a/packages/flutter_tools/lib/src/ios/mac.dart
+++ b/packages/flutter_tools/lib/src/ios/mac.dart
@@ -219,7 +219,7 @@
return new XcodeBuildResult(success: false);
}
- final XcodeProjectInfo projectInfo = new XcodeProjectInfo.fromProjectSync(app.appDirectory);
+ final XcodeProjectInfo projectInfo = xcodeProjectInterpreter.getInfo(app.appDirectory);
if (!projectInfo.targets.contains('Runner')) {
printError('The Xcode project does not define target "Runner" which is needed by Flutter tooling.');
printError('Open Xcode to fix the problem:');
@@ -256,26 +256,22 @@
// copied over to a location that is suitable for Xcodebuild to find them.
final Directory appDirectory = fs.directory(app.appDirectory);
await _addServicesToBundle(appDirectory);
- final InjectPluginsResult injectPluginsResult = injectPlugins();
- final bool hasFlutterPlugins = injectPluginsResult.hasPlugin;
final String previousGeneratedXcconfig = readGeneratedXcconfig(app.appDirectory);
- updateXcodeGeneratedProperties(
+ updateGeneratedXcodeProperties(
projectPath: fs.currentDirectory.path,
buildInfo: buildInfo,
target: target,
- hasPlugins: hasFlutterPlugins,
previewDart2: buildInfo.previewDart2,
);
- if (hasFlutterPlugins) {
+ if (hasPlugins()) {
final String currentGeneratedXcconfig = readGeneratedXcconfig(app.appDirectory);
await cocoaPods.processPods(
- appIosDir: appDirectory,
- iosEngineDir: flutterFrameworkDir(buildInfo.mode),
- isSwift: app.isSwift,
- pluginOrFlutterPodChanged: injectPluginsResult.hasChanged
- || previousGeneratedXcconfig != currentGeneratedXcconfig,
+ appIosDir: appDirectory,
+ iosEngineDir: flutterFrameworkDir(buildInfo.mode),
+ isSwift: app.isSwift,
+ flutterPodChanged: (previousGeneratedXcconfig != currentGeneratedXcconfig),
);
}
@@ -465,7 +461,7 @@
String readGeneratedXcconfig(String appPath) {
final String generatedXcconfigPath =
- fs.path.join(fs.currentDirectory.path, appPath, 'Flutter','Generated.xcconfig');
+ fs.path.join(fs.currentDirectory.path, appPath, 'Flutter', 'Generated.xcconfig');
final File generatedXcconfigFile = fs.file(generatedXcconfigPath);
if (!generatedXcconfigFile.existsSync())
return null;
diff --git a/packages/flutter_tools/lib/src/ios/xcodeproj.dart b/packages/flutter_tools/lib/src/ios/xcodeproj.dart
index 1bc8e79..9a50d30 100644
--- a/packages/flutter_tools/lib/src/ios/xcodeproj.dart
+++ b/packages/flutter_tools/lib/src/ios/xcodeproj.dart
@@ -5,11 +5,13 @@
import 'package:meta/meta.dart';
import '../artifacts.dart';
+import '../base/context.dart';
import '../base/file_system.dart';
import '../base/process.dart';
import '../base/utils.dart';
import '../build_info.dart';
import '../cache.dart';
+import '../flx.dart' as flx;
import '../globals.dart';
final RegExp _settingExpr = new RegExp(r'(\w+)\s*=\s*(.*)$');
@@ -19,11 +21,28 @@
return fs.path.normalize(fs.path.dirname(artifacts.getArtifactPath(Artifact.flutterFramework, TargetPlatform.ios, mode)));
}
-void updateXcodeGeneratedProperties({
+String _generatedXcodePropertiesPath(String projectPath) {
+ return fs.path.join(projectPath, 'ios', 'Flutter', 'Generated.xcconfig');
+}
+
+/// Writes default Xcode properties files in the Flutter project at
+/// [projectPath], if such files do not already exist.
+void generateXcodeProperties(String projectPath) {
+ if (fs.file(_generatedXcodePropertiesPath(projectPath)).existsSync())
+ return;
+ updateGeneratedXcodeProperties(
+ projectPath: projectPath,
+ buildInfo: BuildInfo.debug,
+ target: flx.defaultMainPath,
+ previewDart2: false,
+ );
+}
+
+/// Writes or rewrites Xcode property files with the specified information.
+void updateGeneratedXcodeProperties({
@required String projectPath,
@required BuildInfo buildInfo,
@required String target,
- @required bool hasPlugins,
@required bool previewDart2,
}) {
final StringBuffer localsBuffer = new StringBuffer();
@@ -58,21 +77,42 @@
localsBuffer.writeln('PREVIEW_DART_2=true');
}
- // Add dependency to CocoaPods' generated project only if plugins are used.
- if (hasPlugins)
- localsBuffer.writeln('#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"');
-
- final File localsFile = fs.file(fs.path.join(projectPath, 'ios', 'Flutter', 'Generated.xcconfig'));
+ final File localsFile = fs.file(_generatedXcodePropertiesPath(projectPath));
localsFile.createSync(recursive: true);
localsFile.writeAsStringSync(localsBuffer.toString());
}
-Map<String, String> getXcodeBuildSettings(String xcodeProjPath, String target) {
- final String absProjPath = fs.path.absolute(xcodeProjPath);
- final String out = runCheckedSync(<String>[
- '/usr/bin/xcodebuild', '-project', absProjPath, '-target', target, '-showBuildSettings'
- ]);
- return parseXcodeBuildSettings(out);
+XcodeProjectInterpreter get xcodeProjectInterpreter => context.putIfAbsent(
+ XcodeProjectInterpreter,
+ () => const XcodeProjectInterpreter(),
+);
+
+/// Interpreter of Xcode projects settings.
+class XcodeProjectInterpreter {
+ static const String _executable = '/usr/bin/xcodebuild';
+
+ const XcodeProjectInterpreter();
+
+ bool get canInterpretXcodeProjects => fs.isFileSync(_executable);
+
+ Map<String, String> getBuildSettings(String projectPath, String target) {
+ final String out = runCheckedSync(<String>[
+ _executable,
+ '-project',
+ fs.path.absolute(projectPath),
+ '-target',
+ target,
+ '-showBuildSettings'
+ ], workingDirectory: projectPath);
+ return parseXcodeBuildSettings(out);
+ }
+
+ XcodeProjectInfo getInfo(String projectPath) {
+ final String out = runCheckedSync(<String>[
+ _executable, '-list',
+ ], workingDirectory: projectPath);
+ return new XcodeProjectInfo.fromXcodeBuildOutput(out);
+ }
}
Map<String, String> parseXcodeBuildSettings(String showBuildSettingsOutput) {
@@ -101,13 +141,6 @@
class XcodeProjectInfo {
XcodeProjectInfo(this.targets, this.buildConfigurations, this.schemes);
- factory XcodeProjectInfo.fromProjectSync(String projectPath) {
- final String out = runCheckedSync(<String>[
- '/usr/bin/xcodebuild', '-list',
- ], workingDirectory: projectPath);
- return new XcodeProjectInfo.fromXcodeBuildOutput(out);
- }
-
factory XcodeProjectInfo.fromXcodeBuildOutput(String output) {
final List<String> targets = <String>[];
final List<String> buildConfigurations = <String>[];
diff --git a/packages/flutter_tools/lib/src/plugins.dart b/packages/flutter_tools/lib/src/plugins.dart
index 0d03f04..697cfdc 100644
--- a/packages/flutter_tools/lib/src/plugins.dart
+++ b/packages/flutter_tools/lib/src/plugins.dart
@@ -9,6 +9,7 @@
import 'base/file_system.dart';
import 'dart/package_map.dart';
import 'globals.dart';
+import 'ios/cocoapods.dart';
class Plugin {
final String name;
@@ -80,21 +81,25 @@
/// Returns true if .flutter-plugins has changed, otherwise returns false.
bool _writeFlutterPluginsList(String directory, List<Plugin> plugins) {
- final File pluginsProperties = fs.file(fs.path.join(directory, '.flutter-plugins'));
- final String previousFlutterPlugins =
- pluginsProperties.existsSync() ? pluginsProperties.readAsStringSync() : null;
+ final File pluginsFile = fs.file(fs.path.join(directory, '.flutter-plugins'));
+ final String oldContents = _readFlutterPluginsList(directory);
final String pluginManifest =
plugins.map((Plugin p) => '${p.name}=${escapePath(p.path)}').join('\n');
if (pluginManifest.isNotEmpty) {
- pluginsProperties.writeAsStringSync('$pluginManifest\n');
+ pluginsFile.writeAsStringSync('$pluginManifest\n', flush: true);
} else {
- if (pluginsProperties.existsSync()) {
- pluginsProperties.deleteSync();
- }
+ if (pluginsFile.existsSync())
+ pluginsFile.deleteSync();
}
- final String currentFlutterPlugins =
- pluginsProperties.existsSync() ? pluginsProperties.readAsStringSync() : null;
- return currentFlutterPlugins != previousFlutterPlugins;
+ final String newContents = _readFlutterPluginsList(directory);
+ return oldContents != newContents;
+}
+
+/// Returns the contents of the `.flutter-plugins` file in [directory], or
+/// null if that file does not exist.
+String _readFlutterPluginsList(String directory) {
+ final File pluginsFile = fs.file(fs.path.join(directory, '.flutter-plugins'));
+ return pluginsFile.existsSync() ? pluginsFile.readAsStringSync() : null;
}
const String _androidPluginRegistryTemplate = '''package io.flutter.plugins;
@@ -128,7 +133,7 @@
}
''';
-void _writeAndroidPluginRegistry(String directory, List<Plugin> plugins) {
+void _writeAndroidPluginRegistrant(String directory, List<Plugin> plugins) {
final List<Map<String, dynamic>> androidPlugins = plugins
.where((Plugin p) => p.androidPackage != null && p.pluginClass != null)
.map((Plugin p) => <String, dynamic>{
@@ -187,7 +192,7 @@
@end
''';
-void _writeIOSPluginRegistry(String directory, List<Plugin> plugins) {
+void _writeIOSPluginRegistrant(String directory, List<Plugin> plugins) {
final List<Map<String, dynamic>> iosPlugins = plugins
.where((Plugin p) => p.pluginClass != null)
.map((Plugin p) => <String, dynamic>{
@@ -210,7 +215,6 @@
registryHeaderFile.writeAsStringSync(pluginRegistryHeader);
final File registryImplementationFile = registryDirectory.childFile('GeneratedPluginRegistrant.m');
registryImplementationFile.writeAsStringSync(pluginRegistryImplementation);
-
}
class InjectPluginsResult{
@@ -224,17 +228,30 @@
final bool hasChanged;
}
-/// Finds Flutter plugins in the pubspec.yaml, creates platform injection
-/// registries classes and add them to the build dependencies.
-///
-/// Returns whether any Flutter plugins are added and whether they changed.
-InjectPluginsResult injectPlugins({String directory}) {
+/// Injects plugins found in `pubspec.yaml` into the platform-specific projects.
+void injectPlugins({String directory}) {
directory ??= fs.currentDirectory.path;
+ if (fs.file(fs.path.join(directory, 'example', 'pubspec.yaml')).existsSync()) {
+ // Switch to example app if in plugin project template.
+ directory = fs.path.join(directory, 'example');
+ }
final List<Plugin> plugins = _findPlugins(directory);
- final bool hasPluginsChanged = _writeFlutterPluginsList(directory, plugins);
+ final bool changed = _writeFlutterPluginsList(directory, plugins);
if (fs.isDirectorySync(fs.path.join(directory, 'android')))
- _writeAndroidPluginRegistry(directory, plugins);
- if (fs.isDirectorySync(fs.path.join(directory, 'ios')))
- _writeIOSPluginRegistry(directory, plugins);
- return new InjectPluginsResult(hasPlugin: plugins.isNotEmpty, hasChanged: hasPluginsChanged);
+ _writeAndroidPluginRegistrant(directory, plugins);
+ if (fs.isDirectorySync(fs.path.join(directory, 'ios'))) {
+ _writeIOSPluginRegistrant(directory, plugins);
+ final CocoaPods cocoaPods = const CocoaPods();
+ if (plugins.isNotEmpty)
+ cocoaPods.setupPodfile(directory);
+ if (changed)
+ cocoaPods.invalidatePodInstallOutput(directory);
+ }
+}
+
+/// Returns whether the Flutter project at the specified [directory]
+/// has any plugin dependencies.
+bool hasPlugins({String directory}) {
+ directory ??= fs.currentDirectory.path;
+ return _readFlutterPluginsList(directory) != null;
}
diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart
index 1fd1b4f..7800f2a 100644
--- a/packages/flutter_tools/lib/src/project.dart
+++ b/packages/flutter_tools/lib/src/project.dart
@@ -4,7 +4,11 @@
import 'dart:async';
import 'dart:convert';
+
import 'base/file_system.dart';
+import 'ios/xcodeproj.dart';
+import 'plugins.dart';
+
/// Represents the contents of a Flutter project at the specified [directory].
class FlutterProject {
@@ -43,8 +47,25 @@
/// The Android sub project of this project.
AndroidProject get android => new AndroidProject(directory.childDirectory('android'));
+ /// Returns true if this project is a plugin project.
+ bool get isPluginProject => directory.childDirectory('example').childFile('pubspec.yaml').existsSync();
+
/// The example sub project of this (plugin) project.
FlutterProject get example => new FlutterProject(directory.childDirectory('example'));
+
+ /// Generates project files necessary to make Gradle builds work on Android
+ /// and CocoaPods+Xcode work on iOS.
+ void ensureReadyForPlatformSpecificTooling() {
+ if (!directory.existsSync()) {
+ return;
+ }
+ if (isPluginProject) {
+ example.ensureReadyForPlatformSpecificTooling();
+ } else {
+ injectPlugins(directory: directory.path);
+ generateXcodeProperties(directory.path);
+ }
+ }
}
/// Represents the contents of the ios/ folder of a Flutter project.
diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart
index 9d08b3e..ca48a1a 100644
--- a/packages/flutter_tools/lib/src/runner/flutter_command.dart
+++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart
@@ -21,6 +21,7 @@
import '../doctor.dart';
import '../flx.dart' as flx;
import '../globals.dart';
+import '../project.dart';
import '../usage.dart';
import 'flutter_command_runner.dart';
@@ -272,8 +273,10 @@
if (shouldUpdateCache)
await cache.updateAll();
- if (shouldRunPub)
+ if (shouldRunPub) {
await pubGet(context: PubContext.getVerifyContext(name));
+ new FlutterProject(fs.currentDirectory).ensureReadyForPlatformSpecificTooling();
+ }
setupApplicationPackages();
diff --git a/packages/flutter_tools/test/commands/packages_test.dart b/packages/flutter_tools/test/commands/packages_test.dart
index 86bbc28..34f6fb8 100644
--- a/packages/flutter_tools/test/commands/packages_test.dart
+++ b/packages/flutter_tools/test/commands/packages_test.dart
@@ -29,9 +29,19 @@
temp.deleteSync(recursive: true);
});
- Future<String> runCommand(String verb, { List<String> args }) async {
+ Future<String> createProjectWithPlugin(String plugin) async {
final String projectPath = await createProject(temp);
+ final File pubspec = fs.file(fs.path.join(projectPath, 'pubspec.yaml'));
+ String content = await pubspec.readAsString();
+ content = content.replaceFirst(
+ '\ndependencies:\n',
+ '\ndependencies:\n $plugin:\n',
+ );
+ await pubspec.writeAsString(content, flush: true);
+ return projectPath;
+ }
+ Future<Null> runCommandIn(String projectPath, String verb, { List<String> args }) async {
final PackagesCommand command = new PackagesCommand();
final CommandRunner<Null> runner = createTestCommandRunner(command);
@@ -41,31 +51,148 @@
commandArgs.add(projectPath);
await runner.run(commandArgs);
-
- return projectPath;
}
void expectExists(String projectPath, String relPath) {
- expect(fs.isFileSync(fs.path.join(projectPath, relPath)), true);
+ expect(
+ fs.isFileSync(fs.path.join(projectPath, relPath)),
+ true,
+ reason: '$projectPath/$relPath should exist, but does not',
+ );
}
- // Verify that we create a project that is well-formed.
- testUsingContext('get', () async {
- final String projectPath = await runCommand('get');
- expectExists(projectPath, 'lib/main.dart');
- expectExists(projectPath, '.packages');
+ void expectContains(String projectPath, String relPath, String substring) {
+ expectExists(projectPath, relPath);
+ expect(
+ fs.file(fs.path.join(projectPath, relPath)).readAsStringSync(),
+ contains(substring),
+ reason: '$projectPath/$relPath has unexpected content'
+ );
+ }
+
+ void expectNotExists(String projectPath, String relPath) {
+ expect(
+ fs.isFileSync(fs.path.join(projectPath, relPath)),
+ false,
+ reason: '$projectPath/$relPath should not exist, but does',
+ );
+ }
+
+ void expectNotContains(String projectPath, String relPath, String substring) {
+ expectExists(projectPath, relPath);
+ expect(
+ fs.file(fs.path.join(projectPath, relPath)).readAsStringSync(),
+ isNot(contains(substring)),
+ reason: '$projectPath/$relPath has unexpected content',
+ );
+ }
+
+ const List<String> pubOutput = const <String>[
+ '.packages',
+ 'pubspec.lock',
+ ];
+
+ const List<String> pluginRegistrants = const <String>[
+ 'ios/Runner/GeneratedPluginRegistrant.h',
+ 'ios/Runner/GeneratedPluginRegistrant.m',
+ 'android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java',
+ ];
+
+ const List<String> pluginWitnesses = const <String>[
+ '.flutter-plugins',
+ 'ios/Podfile',
+ ];
+
+ const Map<String, String> pluginContentWitnesses = const <String, String>{
+ 'ios/Flutter/Debug.xcconfig': '#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"',
+ 'ios/Flutter/Release.xcconfig': '#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"',
+ };
+
+ void expectDependenciesResolved(String projectPath) {
+ for (String output in pubOutput) {
+ expectExists(projectPath, output);
+ }
+ }
+
+ void expectZeroPluginsInjected(String projectPath) {
+ for (final String registrant in pluginRegistrants) {
+ expectExists(projectPath, registrant);
+ }
+ for (final String witness in pluginWitnesses) {
+ expectNotExists(projectPath, witness);
+ }
+ pluginContentWitnesses.forEach((String witness, String content) {
+ expectNotContains(projectPath, witness, content);
+ });
+ }
+
+ void expectPluginInjected(String projectPath) {
+ for (final String registrant in pluginRegistrants) {
+ expectExists(projectPath, registrant);
+ }
+ for (final String witness in pluginWitnesses) {
+ expectExists(projectPath, witness);
+ }
+ pluginContentWitnesses.forEach((String witness, String content) {
+ expectContains(projectPath, witness, content);
+ });
+ }
+
+ void removeGeneratedFiles(String projectPath) {
+ final Iterable<String> allFiles = <List<String>>[
+ pubOutput,
+ pluginRegistrants,
+ pluginWitnesses,
+ ].expand((List<String> list) => list);
+ for (String path in allFiles) {
+ final File file = fs.file(fs.path.join(projectPath, path));
+ if (file.existsSync())
+ file.deleteSync();
+ }
+ }
+
+ testUsingContext('get fetches packages', () async {
+ final String projectPath = await createProject(temp);
+
+ removeGeneratedFiles(projectPath);
+
+ await runCommandIn(projectPath, 'get');
+
+ expectDependenciesResolved(projectPath);
+ expectZeroPluginsInjected(projectPath);
}, timeout: allowForRemotePubInvocation);
- testUsingContext('get --offline', () async {
- final String projectPath = await runCommand('get', args: <String>['--offline']);
- expectExists(projectPath, 'lib/main.dart');
- expectExists(projectPath, '.packages');
- });
+ testUsingContext('get --offline fetches packages', () async {
+ final String projectPath = await createProject(temp);
- testUsingContext('upgrade', () async {
- final String projectPath = await runCommand('upgrade');
- expectExists(projectPath, 'lib/main.dart');
- expectExists(projectPath, '.packages');
+ removeGeneratedFiles(projectPath);
+
+ await runCommandIn(projectPath, 'get', args: <String>['--offline']);
+
+ expectDependenciesResolved(projectPath);
+ expectZeroPluginsInjected(projectPath);
+ }, timeout: allowForCreateFlutterProject);
+
+ testUsingContext('upgrade fetches packages', () async {
+ final String projectPath = await createProject(temp);
+
+ removeGeneratedFiles(projectPath);
+
+ await runCommandIn(projectPath, 'upgrade');
+
+ expectDependenciesResolved(projectPath);
+ expectZeroPluginsInjected(projectPath);
+ }, timeout: allowForRemotePubInvocation);
+
+ testUsingContext('get fetches packages and injects plugin', () async {
+ final String projectPath = await createProjectWithPlugin('path_provider');
+
+ removeGeneratedFiles(projectPath);
+
+ await runCommandIn(projectPath, 'get');
+
+ expectDependenciesResolved(projectPath);
+ expectPluginInjected(projectPath);
}, timeout: allowForRemotePubInvocation);
});
diff --git a/packages/flutter_tools/test/ios/cocoapods_test.dart b/packages/flutter_tools/test/ios/cocoapods_test.dart
index 531c8ab..77c1e6d 100644
--- a/packages/flutter_tools/test/ios/cocoapods_test.dart
+++ b/packages/flutter_tools/test/ios/cocoapods_test.dart
@@ -6,9 +6,11 @@
import 'package:file/file.dart';
import 'package:file/memory.dart';
+import 'package:flutter_tools/src/base/common.dart';
import 'package:flutter_tools/src/base/io.dart';
-import 'package:flutter_tools/src/ios/cocoapods.dart';
import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/ios/cocoapods.dart';
+import 'package:flutter_tools/src/ios/xcodeproj.dart';
import 'package:mockito/mockito.dart';
import 'package:process/process.dart';
import 'package:test/test.dart';
@@ -18,6 +20,7 @@
void main() {
FileSystem fs;
ProcessManager mockProcessManager;
+ MockXcodeProjectInterpreter mockXcodeProjectInterpreter;
Directory projectUnderTest;
CocoaPods cocoaPodsUnderTest;
@@ -25,7 +28,9 @@
Cache.flutterRoot = 'flutter';
fs = new MemoryFileSystem();
mockProcessManager = new MockProcessManager();
+ mockXcodeProjectInterpreter = new MockXcodeProjectInterpreter();
projectUnderTest = fs.directory(fs.path.join('project', 'ios'))..createSync(recursive: true);
+
fs.file(fs.path.join(
Cache.flutterRoot, 'packages', 'flutter_tools', 'templates', 'cocoapods', 'Podfile-objc'
))
@@ -45,97 +50,122 @@
)).thenReturn(exitsHappy);
});
- testUsingContext(
- 'create objective-c Podfile when not present',
- () async {
- await cocoaPodsUnderTest.processPods(
- appIosDir: projectUnderTest,
- iosEngineDir: 'engine/path',
- );
- expect(fs.file(fs.path.join('project', 'ios', 'Podfile')).readAsStringSync() , 'Objective-C podfile template');
- verify(mockProcessManager.run(
- <String>['pod', 'install', '--verbose'],
- workingDirectory: 'project/ios',
- environment: <String, String>{'FLUTTER_FRAMEWORK_DIR': 'engine/path', 'COCOAPODS_DISABLE_STATS': 'true'},
- ));
- },
- overrides: <Type, Generator>{
- FileSystem: () => fs,
- ProcessManager: () => mockProcessManager,
- },
- );
+ group('Setup Podfile', () {
+ File podfile;
+ File debugConfigFile;
+ File releaseConfigFile;
- testUsingContext(
- 'create swift Podfile if swift',
- () async {
- await cocoaPodsUnderTest.processPods(
- appIosDir: projectUnderTest,
- iosEngineDir: 'engine/path',
- isSwift: true,
- );
- expect(fs.file(fs.path.join('project', 'ios', 'Podfile')).readAsStringSync() , 'Swift podfile template');
- verify(mockProcessManager.run(
- <String>['pod', 'install', '--verbose'],
- workingDirectory: 'project/ios',
- environment: <String, String>{'FLUTTER_FRAMEWORK_DIR': 'engine/path', 'COCOAPODS_DISABLE_STATS': 'true'},
- ));
- },
- overrides: <Type, Generator>{
- FileSystem: () => fs,
- ProcessManager: () => mockProcessManager,
- },
- );
+ setUp(() {
+ debugConfigFile = fs.file(fs.path.join('project', 'ios', 'Flutter', 'Debug.xcconfig'));
+ releaseConfigFile = fs.file(fs.path.join('project', 'ios', 'Flutter', 'Release.xcconfig'));
+ podfile = fs.file(fs.path.join('project', 'ios', 'Podfile'));
+ });
- testUsingContext(
- 'do not recreate Podfile when present',
- () async {
- fs.file(fs.path.join('project', 'ios', 'Podfile'))
- ..createSync()
- ..writeAsString('Existing Podfile');
- await cocoaPodsUnderTest.processPods(
- appIosDir: projectUnderTest,
- iosEngineDir: 'engine/path',
- );
- expect(fs.file(fs.path.join('project', 'ios', 'Podfile')).readAsStringSync() , 'Existing Podfile');
- verify(mockProcessManager.run(
- <String>['pod', 'install', '--verbose'],
- workingDirectory: 'project/ios',
- environment: <String, String>{'FLUTTER_FRAMEWORK_DIR': 'engine/path', 'COCOAPODS_DISABLE_STATS': 'true'},
- ));
- },
- overrides: <Type, Generator>{
- FileSystem: () => fs,
- ProcessManager: () => mockProcessManager,
- },
- );
+ testUsingContext('creates objective-c Podfile when not present', () {
+ cocoaPodsUnderTest.setupPodfile('project');
- testUsingContext(
- 'missing CocoaPods throws',
- () async {
+ expect(podfile.readAsStringSync(), 'Objective-C podfile template');
+ }, overrides: <Type, Generator>{
+ FileSystem: () => fs,
+ });
+
+ testUsingContext('creates swift Podfile if swift', () {
+ when(mockXcodeProjectInterpreter.canInterpretXcodeProjects).thenReturn(true);
+ when(mockXcodeProjectInterpreter.getBuildSettings(any, any)).thenReturn(<String, String>{
+ 'SWIFT_VERSION': '4.0',
+ });
+
+ cocoaPodsUnderTest.setupPodfile('project');
+
+ expect(podfile.readAsStringSync(), 'Swift podfile template');
+ }, overrides: <Type, Generator>{
+ FileSystem: () => fs,
+ XcodeProjectInterpreter: () => mockXcodeProjectInterpreter,
+ });
+
+ testUsingContext('does not recreate Podfile when already present', () {
+ podfile..createSync()..writeAsStringSync('Existing Podfile');
+
+ cocoaPodsUnderTest.setupPodfile('project');
+
+ expect(podfile.readAsStringSync(), 'Existing Podfile');
+ }, overrides: <Type, Generator>{
+ FileSystem: () => fs,
+ });
+
+ testUsingContext('does not create Podfile when we cannot interpret Xcode projects', () {
+ when(mockXcodeProjectInterpreter.canInterpretXcodeProjects).thenReturn(false);
+
+ cocoaPodsUnderTest.setupPodfile('project');
+
+ expect(podfile.existsSync(), false);
+ }, overrides: <Type, Generator>{
+ FileSystem: () => fs,
+ XcodeProjectInterpreter: () => mockXcodeProjectInterpreter,
+ });
+
+ testUsingContext('includes Pod config in xcconfig files, if not present', () {
+ podfile..createSync()..writeAsStringSync('Existing Podfile');
+ debugConfigFile..createSync(recursive: true)..writeAsStringSync('Existing debug config');
+ releaseConfigFile..createSync(recursive: true)..writeAsStringSync('Existing release config');
+
+ cocoaPodsUnderTest.setupPodfile('project');
+
+ final String debugContents = debugConfigFile.readAsStringSync();
+ expect(debugContents, contains(
+ '#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"\n'));
+ expect(debugContents, contains('Existing debug config'));
+ final String releaseContents = releaseConfigFile.readAsStringSync();
+ expect(releaseContents, contains(
+ '#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"\n'));
+ expect(releaseContents, contains('Existing release config'));
+ }, overrides: <Type, Generator>{
+ FileSystem: () => fs,
+ });
+ });
+
+ group('Process pods', () {
+ testUsingContext('prints error, if CocoaPods is not installed', () async {
+ projectUnderTest.childFile('Podfile').createSync();
cocoaPodsUnderTest = const TestCocoaPods(false);
+ await cocoaPodsUnderTest.processPods(
+ appIosDir: projectUnderTest,
+ iosEngineDir: 'engine/path',
+ );
+ verifyNever(mockProcessManager.run(
+ typed<List<String>>(any),
+ workingDirectory: any,
+ environment: typed<Map<String, String>>(any, named: 'environment'),
+ ));
+ expect(testLogger.errorText, contains('not installed'));
+ expect(testLogger.errorText, contains('Skipping pod install'));
+ }, overrides: <Type, Generator>{
+ FileSystem: () => fs,
+ ProcessManager: () => mockProcessManager,
+ });
+
+ testUsingContext('throws, if Podfile is missing.', () async {
+ cocoaPodsUnderTest = const TestCocoaPods(true);
try {
await cocoaPodsUnderTest.processPods(
appIosDir: projectUnderTest,
iosEngineDir: 'engine/path',
);
- fail('Expected tool error');
- } catch (ToolExit) {
+ fail('ToolExit expected');
+ } catch(e) {
+ expect(e, const isInstanceOf<ToolExit>());
verifyNever(mockProcessManager.run(
- <String>['pod', 'install', '--verbose'],
- workingDirectory: 'project/ios',
- environment: <String, String>{'FLUTTER_FRAMEWORK_DIR': 'engine/path', 'COCOAPODS_DISABLE_STATS': 'true'},
+ typed<List<String>>(any),
+ workingDirectory: any,
+ environment: typed<Map<String, String>>(any, named: 'environment'),
));
}
- },
- overrides: <Type, Generator>{
+ }, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => mockProcessManager,
- },
- );
+ });
- testUsingContext(
- 'outdated specs repo should print error',
- () async {
+ testUsingContext('throws, if specs repo is outdated.', () async {
fs.file(fs.path.join('project', 'ios', 'Podfile'))
..createSync()
..writeAsString('Existing Podfile');
@@ -143,7 +173,10 @@
when(mockProcessManager.run(
<String>['pod', 'install', '--verbose'],
workingDirectory: 'project/ios',
- environment: <String, String>{'FLUTTER_FRAMEWORK_DIR': 'engine/path', 'COCOAPODS_DISABLE_STATS': 'true'},
+ environment: <String, String>{
+ 'FLUTTER_FRAMEWORK_DIR': 'engine/path',
+ 'COCOAPODS_DISABLE_STATS': 'true',
+ },
)).thenReturn(new ProcessResult(
1,
1,
@@ -167,78 +200,152 @@
await cocoaPodsUnderTest.processPods(
appIosDir: projectUnderTest,
iosEngineDir: 'engine/path',
- ); expect(fs.file(fs.path.join('project', 'ios', 'Podfile')).readAsStringSync() , 'Existing Podfile');
- fail('Exception expected');
- } catch (ToolExit) {
- expect(testLogger.errorText, contains("CocoaPods's specs repository is too out-of-date to satisfy dependencies"));
+ );
+ fail('ToolExit expected');
+ } catch (e) {
+ expect(e, const isInstanceOf<ToolExit>());
+ expect(
+ testLogger.errorText,
+ contains("CocoaPods's specs repository is too out-of-date to satisfy dependencies"),
+ );
}
- },
- overrides: <Type, Generator>{
+ }, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => mockProcessManager,
- },
- );
+ });
- testUsingContext(
- 'Run pod install if plugins or flutter framework have changes.',
- () async {
- fs.file(fs.path.join('project', 'ios', 'Podfile'))
+ testUsingContext('run pod install, if Podfile.lock is missing', () async {
+ projectUnderTest.childFile('Podfile')
..createSync()
..writeAsString('Existing Podfile');
- fs.file(fs.path.join('project', 'ios', 'Podfile.lock'))
- ..createSync()
- ..writeAsString('Existing lock files.');
- fs.file(fs.path.join('project', 'ios', 'Pods','Manifest.lock'))
+ projectUnderTest.childFile('Pods/Manifest.lock')
..createSync(recursive: true)
- ..writeAsString('Existing lock files.');
+ ..writeAsString('Existing lock file.');
await cocoaPodsUnderTest.processPods(
- appIosDir: projectUnderTest,
- iosEngineDir: 'engine/path',
- pluginOrFlutterPodChanged: true
+ appIosDir: projectUnderTest,
+ iosEngineDir: 'engine/path',
+ flutterPodChanged: false,
);
verify(mockProcessManager.run(
<String>['pod', 'install', '--verbose'],
workingDirectory: 'project/ios',
environment: <String, String>{'FLUTTER_FRAMEWORK_DIR': 'engine/path', 'COCOAPODS_DISABLE_STATS': 'true'},
));
- },
- overrides: <Type, Generator>{
+ }, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => mockProcessManager,
- },
- );
+ });
- testUsingContext(
- 'Skip pod install if plugins and flutter framework remain unchanged.',
- () async {
- fs.file(fs.path.join('project', 'ios', 'Podfile'))
+ testUsingContext('runs pod install, if Manifest.lock is missing', () async {
+ projectUnderTest.childFile('Podfile')
..createSync()
..writeAsString('Existing Podfile');
- fs.file(fs.path.join('project', 'ios', 'Podfile.lock'))
+ projectUnderTest.childFile('Podfile.lock')
..createSync()
- ..writeAsString('Existing lock files.');
- fs.file(fs.path.join('project', 'ios', 'Pods','Manifest.lock'))
- ..createSync(recursive: true)
- ..writeAsString('Existing lock files.');
+ ..writeAsString('Existing lock file.');
await cocoaPodsUnderTest.processPods(
- appIosDir: projectUnderTest,
- iosEngineDir: 'engine/path',
- pluginOrFlutterPodChanged: false
+ appIosDir: projectUnderTest,
+ iosEngineDir: 'engine/path',
+ flutterPodChanged: false,
);
- verifyNever(mockProcessManager.run(
+ verify(mockProcessManager.run(
<String>['pod', 'install', '--verbose'],
workingDirectory: 'project/ios',
- environment: <String, String>{'FLUTTER_FRAMEWORK_DIR': 'engine/path', 'COCOAPODS_DISABLE_STATS': 'true'},
+ environment: <String, String>{
+ 'FLUTTER_FRAMEWORK_DIR': 'engine/path',
+ 'COCOAPODS_DISABLE_STATS': 'true',
+ },
));
- },
- overrides: <Type, Generator>{
+ }, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => mockProcessManager,
- },
- );
+ });
+
+ testUsingContext('runs pod install, if Manifest.lock different from Podspec.lock', () async {
+ projectUnderTest.childFile('Podfile')
+ ..createSync()
+ ..writeAsString('Existing Podfile');
+ projectUnderTest.childFile('Podfile.lock')
+ ..createSync()
+ ..writeAsString('Existing lock file.');
+ projectUnderTest.childFile('Pods/Manifest.lock')
+ ..createSync(recursive: true)
+ ..writeAsString('Different lock file.');
+ await cocoaPodsUnderTest.processPods(
+ appIosDir: projectUnderTest,
+ iosEngineDir: 'engine/path',
+ flutterPodChanged: false,
+ );
+ verify(mockProcessManager.run(
+ <String>['pod', 'install', '--verbose'],
+ workingDirectory: 'project/ios',
+ environment: <String, String>{
+ 'FLUTTER_FRAMEWORK_DIR': 'engine/path',
+ 'COCOAPODS_DISABLE_STATS': 'true',
+ },
+ ));
+ }, overrides: <Type, Generator>{
+ FileSystem: () => fs,
+ ProcessManager: () => mockProcessManager,
+ });
+
+ testUsingContext('runs pod install, if flutter framework changed', () async {
+ projectUnderTest.childFile('Podfile')
+ ..createSync()
+ ..writeAsString('Existing Podfile');
+ projectUnderTest.childFile('Podfile.lock')
+ ..createSync()
+ ..writeAsString('Existing lock file.');
+ projectUnderTest.childFile('Pods/Manifest.lock')
+ ..createSync(recursive: true)
+ ..writeAsString('Existing lock file.');
+ await cocoaPodsUnderTest.processPods(
+ appIosDir: projectUnderTest,
+ iosEngineDir: 'engine/path',
+ flutterPodChanged: true,
+ );
+ verify(mockProcessManager.run(
+ <String>['pod', 'install', '--verbose'],
+ workingDirectory: 'project/ios',
+ environment: <String, String>{
+ 'FLUTTER_FRAMEWORK_DIR': 'engine/path',
+ 'COCOAPODS_DISABLE_STATS': 'true',
+ },
+ ));
+ }, overrides: <Type, Generator>{
+ FileSystem: () => fs,
+ ProcessManager: () => mockProcessManager,
+ });
+
+ testUsingContext('skips pod install, if nothing changed', () async {
+ projectUnderTest.childFile('Podfile')
+ ..createSync()
+ ..writeAsString('Existing Podfile');
+ projectUnderTest.childFile('Podfile.lock')
+ ..createSync()
+ ..writeAsString('Existing lock file.');
+ projectUnderTest.childFile('Pods/Manifest.lock')
+ ..createSync(recursive: true)
+ ..writeAsString('Existing lock file.');
+ await cocoaPodsUnderTest.processPods(
+ appIosDir: projectUnderTest,
+ iosEngineDir: 'engine/path',
+ flutterPodChanged: false,
+ );
+ verifyNever(mockProcessManager.run(
+ typed<List<String>>(any),
+ workingDirectory: any,
+ environment: typed<Map<String, String>>(any, named: 'environment'),
+ ));
+ }, overrides: <Type, Generator>{
+ FileSystem: () => fs,
+ ProcessManager: () => mockProcessManager,
+ });
+ });
}
class MockProcessManager extends Mock implements ProcessManager {}
+class MockXcodeProjectInterpreter extends Mock implements XcodeProjectInterpreter {}
class TestCocoaPods extends CocoaPods {
const TestCocoaPods([this._hasCocoaPods = true]);
diff --git a/packages/flutter_tools/test/project_test.dart b/packages/flutter_tools/test/project_test.dart
index 2f1a3ff..6ef5bbb 100644
--- a/packages/flutter_tools/test/project_test.dart
+++ b/packages/flutter_tools/test/project_test.dart
@@ -17,6 +17,29 @@
final Directory directory = fs.directory('myproject');
expect(new FlutterProject(directory).directory, directory);
});
+ group('ensure ready for platform-specific tooling', () {
+ testInMemory('does nothing, if project is not created', () async {
+ final FlutterProject project = someProject();
+ project.ensureReadyForPlatformSpecificTooling();
+ expect(project.directory.existsSync(), isFalse);
+ });
+ testInMemory('injects plugins', () async {
+ final FlutterProject project = aProjectWithIos();
+ project.ensureReadyForPlatformSpecificTooling();
+ expect(project.ios.directory.childFile('Runner/GeneratedPluginRegistrant.h').existsSync(), isTrue);
+ });
+ testInMemory('generates Xcode configuration', () async {
+ final FlutterProject project = aProjectWithIos();
+ project.ensureReadyForPlatformSpecificTooling();
+ expect(project.ios.directory.childFile('Flutter/Generated.xcconfig').existsSync(), isTrue);
+ });
+ testInMemory('generates files in plugin example project', () async {
+ final FlutterProject project = aPluginProject();
+ project.ensureReadyForPlatformSpecificTooling();
+ expect(project.example.ios.directory.childFile('Runner/GeneratedPluginRegistrant.h').existsSync(), isTrue);
+ expect(project.example.ios.directory.childFile('Flutter/Generated.xcconfig').existsSync(), isTrue);
+ });
+ });
group('organization names set', () {
testInMemory('is empty, if project not created', () async {
final FlutterProject project = someProject();
@@ -71,8 +94,23 @@
});
}
-FlutterProject someProject() =>
- new FlutterProject(fs.directory('some_project'));
+FlutterProject someProject() => new FlutterProject(fs.directory('some_project'));
+
+FlutterProject aProjectWithIos() {
+ final Directory directory = fs.directory('ios_project');
+ directory.childFile('pubspec.yaml').createSync(recursive: true);
+ directory.childFile('.packages').createSync(recursive: true);
+ directory.childDirectory('ios').createSync(recursive: true);
+ return new FlutterProject(directory);
+}
+
+FlutterProject aPluginProject() {
+ final Directory directory = fs.directory('plugin_project/example');
+ directory.childFile('pubspec.yaml').createSync(recursive: true);
+ directory.childFile('.packages').createSync(recursive: true);
+ directory.childDirectory('ios').createSync(recursive: true);
+ return new FlutterProject(directory.parent);
+}
void testInMemory(String description, Future<Null> testMethod()) {
testUsingContext(
@@ -84,6 +122,13 @@
);
}
+void addPubPackages(Directory directory) {
+ directory.childFile('pubspec.yaml')
+ ..createSync(recursive: true);
+ directory.childFile('.packages')
+ ..createSync(recursive: true);
+}
+
void addIosWithBundleId(Directory directory, String id) {
directory
.childDirectory('ios')
diff --git a/packages/flutter_tools/test/src/context.dart b/packages/flutter_tools/test/src/context.dart
index 3c5fe1b..dbe5188 100644
--- a/packages/flutter_tools/test/src/context.dart
+++ b/packages/flutter_tools/test/src/context.dart
@@ -19,6 +19,7 @@
import 'package:flutter_tools/src/doctor.dart';
import 'package:flutter_tools/src/ios/mac.dart';
import 'package:flutter_tools/src/ios/simulators.dart';
+import 'package:flutter_tools/src/ios/xcodeproj.dart';
import 'package:flutter_tools/src/run_hot.dart';
import 'package:flutter_tools/src/usage.dart';
import 'package:flutter_tools/src/version.dart';
@@ -50,6 +51,7 @@
..putIfAbsent(OperatingSystemUtils, () => new MockOperatingSystemUtils())
..putIfAbsent(PortScanner, () => new MockPortScanner())
..putIfAbsent(Xcode, () => new Xcode())
+ ..putIfAbsent(XcodeProjectInterpreter, () => new MockXcodeProjectInterpreter())
..putIfAbsent(IOSSimulatorUtils, () {
final MockIOSSimulatorUtils mock = new MockIOSSimulatorUtils();
when(mock.getAttachedDevices()).thenReturn(<IOSSimulator>[]);
@@ -262,6 +264,25 @@
void printWelcome() { }
}
+class MockXcodeProjectInterpreter implements XcodeProjectInterpreter {
+ @override
+ bool get canInterpretXcodeProjects => true;
+
+ @override
+ Map<String, String> getBuildSettings(String projectPath, String target) {
+ return <String, String>{};
+ }
+
+ @override
+ XcodeProjectInfo getInfo(String projectPath) {
+ return new XcodeProjectInfo(
+ <String>['Runner'],
+ <String>['Debug', 'Release'],
+ <String>['Runner'],
+ );
+ }
+}
+
class MockFlutterVersion extends Mock implements FlutterVersion {}
class MockClock extends Mock implements Clock {}