mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Move plugin injection to just after pub get (#14560)
This commit is contained in:
parent
d260294752
commit
b3e4976ca2
12
dev/devicelab/bin/tasks/plugin_test.dart
Normal file
12
dev/devicelab/bin/tasks/plugin_test.dart
Normal file
@ -0,0 +1,12 @@
|
||||
// Copyright (c) 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:flutter_devicelab/tasks/plugin_tests.dart';
|
||||
import 'package:flutter_devicelab/framework/framework.dart';
|
||||
|
||||
Future<Null> main() async {
|
||||
await task(new PluginTest('apk'));
|
||||
}
|
||||
12
dev/devicelab/bin/tasks/plugin_test_ios.dart
Normal file
12
dev/devicelab/bin/tasks/plugin_test_ios.dart
Normal file
@ -0,0 +1,12 @@
|
||||
// Copyright (c) 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:flutter_devicelab/tasks/plugin_tests.dart';
|
||||
import 'package:flutter_devicelab/framework/framework.dart';
|
||||
|
||||
Future<Null> main() async {
|
||||
await task(new PluginTest('ios'));
|
||||
}
|
||||
12
dev/devicelab/bin/tasks/plugin_test_win.dart
Normal file
12
dev/devicelab/bin/tasks/plugin_test_win.dart
Normal file
@ -0,0 +1,12 @@
|
||||
// Copyright (c) 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:flutter_devicelab/tasks/plugin_tests.dart';
|
||||
import 'package:flutter_devicelab/framework/framework.dart';
|
||||
|
||||
Future<Null> main() async {
|
||||
await task(new PluginTest('apk'));
|
||||
}
|
||||
73
dev/devicelab/lib/tasks/plugin_tests.dart
Normal file
73
dev/devicelab/lib/tasks/plugin_tests.dart
Normal file
@ -0,0 +1,73 @@
|
||||
// Copyright (c) 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 'dart:io';
|
||||
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:flutter_devicelab/framework/framework.dart';
|
||||
import 'package:flutter_devicelab/framework/utils.dart';
|
||||
|
||||
/// Defines task that creates new Flutter project, adds a plugin, and then
|
||||
/// builds the specified [buildTarget].
|
||||
class PluginTest {
|
||||
final String buildTarget;
|
||||
|
||||
PluginTest(this.buildTarget);
|
||||
|
||||
Future<TaskResult> call() async {
|
||||
section('Create Flutter project');
|
||||
final Directory tmp = await Directory.systemTemp.createTemp('plugin');
|
||||
final FlutterProject project = await FlutterProject.create(tmp, 'hello');
|
||||
try {
|
||||
section('Add plugin');
|
||||
await project.addPlugin('path_provider');
|
||||
|
||||
section('Build');
|
||||
await project.build(buildTarget);
|
||||
|
||||
return new TaskResult.success(null);
|
||||
} catch (e) {
|
||||
return new TaskResult.failure(e.toString());
|
||||
} finally {
|
||||
await project.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FlutterProject {
|
||||
FlutterProject(this.parent, this.name);
|
||||
|
||||
final Directory parent;
|
||||
final String name;
|
||||
|
||||
static Future<FlutterProject> create(Directory directory, String name) async {
|
||||
await inDirectory(directory, () async {
|
||||
await flutter('create', options: <String>[name]);
|
||||
});
|
||||
return new FlutterProject(directory, name);
|
||||
}
|
||||
|
||||
String get rootPath => path.join(parent.path, name);
|
||||
|
||||
Future<Null> addPlugin(String plugin) async {
|
||||
final File pubspec = new File(path.join(rootPath, 'pubspec.yaml'));
|
||||
String content = await pubspec.readAsString();
|
||||
content = content.replaceFirst(
|
||||
'\ndependencies:\n',
|
||||
'\ndependencies:\n $plugin:\n',
|
||||
);
|
||||
await pubspec.writeAsString(content, flush: true);
|
||||
}
|
||||
|
||||
Future<Null> build(String target) async {
|
||||
await inDirectory(new Directory(rootPath), () async {
|
||||
await flutter('build', options: <String>[target]);
|
||||
});
|
||||
}
|
||||
|
||||
Future<Null> delete() async {
|
||||
await parent.delete(recursive: true);
|
||||
}
|
||||
}
|
||||
@ -237,6 +237,13 @@ tasks:
|
||||
stage: devicelab
|
||||
required_agent_capabilities: ["linux/android"]
|
||||
|
||||
plugin_test:
|
||||
description: >
|
||||
Checks that the project template works and supports plugins.
|
||||
stage: devicelab
|
||||
required_agent_capabilities: ["linux/android"]
|
||||
flaky: true
|
||||
|
||||
flutter_gallery_instrumentation_test:
|
||||
description: >
|
||||
Same as flutter_gallery__transition_perf but uses Android instrumentation
|
||||
@ -253,6 +260,13 @@ tasks:
|
||||
stage: devicelab_ios
|
||||
required_agent_capabilities: ["mac/ios"]
|
||||
|
||||
plugin_test_ios:
|
||||
description: >
|
||||
Checks that the project template works and supports plugins on iOS.
|
||||
stage: devicelab_ios
|
||||
required_agent_capabilities: ["mac/ios"]
|
||||
flaky: true
|
||||
|
||||
external_ui_integration_test_ios:
|
||||
description: >
|
||||
Checks that external UIs work on iOS.
|
||||
@ -347,6 +361,13 @@ tasks:
|
||||
stage: devicelab_win
|
||||
required_agent_capabilities: ["windows/android"]
|
||||
|
||||
plugin_test_win:
|
||||
description: >
|
||||
Checks that the project template works and supports plugins on Windows.
|
||||
stage: devicelab_win
|
||||
required_agent_capabilities: ["windows/android"]
|
||||
flaky: true
|
||||
|
||||
hot_mode_dev_cycle_win__benchmark:
|
||||
description: >
|
||||
Measures the performance of Dart VM hot patching feature on Windows.
|
||||
|
||||
@ -16,7 +16,6 @@ import '../base/utils.dart';
|
||||
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> _gradleProject() async {
|
||||
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(
|
||||
|
||||
@ -181,11 +181,11 @@ abstract class IOSApp extends ApplicationPackage {
|
||||
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,
|
||||
);
|
||||
|
||||
@ -14,14 +14,10 @@ import '../base/common.dart';
|
||||
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 @@ class CreateCommand extends FlutterCommand {
|
||||
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)
|
||||
|
||||
@ -24,7 +24,8 @@ class InjectPluginsCommand extends FlutterCommand {
|
||||
|
||||
@override
|
||||
Future<Null> runCommand() async {
|
||||
final bool result = injectPlugins().hasPlugin;
|
||||
injectPlugins();
|
||||
final bool result = hasPlugins();
|
||||
if (result) {
|
||||
printStatus('GeneratedPluginRegistrants successfully written.');
|
||||
} else {
|
||||
|
||||
@ -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 @@ class PackagesGetCommand extends FlutterCommand {
|
||||
offline: argResults['offline'],
|
||||
checkLastModified: false,
|
||||
);
|
||||
new FlutterProject(fs.directory(target)).ensureReadyForPlatformSpecificTooling();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -16,6 +16,7 @@ import '../base/process_manager.dart';
|
||||
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 @@ class CocoaPods {
|
||||
// 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 @@ class CocoaPods {
|
||||
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 {
|
||||
|
||||
@ -217,7 +217,7 @@ Future<XcodeBuildResult> buildXcodeProject({
|
||||
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:');
|
||||
@ -254,26 +254,22 @@ Future<XcodeBuildResult> buildXcodeProject({
|
||||
// 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),
|
||||
);
|
||||
}
|
||||
|
||||
@ -419,7 +415,7 @@ Future<XcodeBuildResult> buildXcodeProject({
|
||||
|
||||
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;
|
||||
|
||||
@ -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 @@ String flutterFrameworkDir(BuildMode mode) {
|
||||
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 @@ void updateXcodeGeneratedProperties({
|
||||
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 @@ String substituteXcodeVariables(String str, Map<String, String> xcodeBuildSettin
|
||||
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>[];
|
||||
|
||||
@ -9,6 +9,7 @@ import 'package:yaml/yaml.dart';
|
||||
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 @@ List<Plugin> _findPlugins(String directory) {
|
||||
|
||||
/// 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 @@ public final class GeneratedPluginRegistrant {
|
||||
}
|
||||
''';
|
||||
|
||||
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 @@ const String _iosPluginRegistryImplementationTemplate = '''//
|
||||
@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 @@ void _writeIOSPluginRegistry(String directory, List<Plugin> plugins) {
|
||||
registryHeaderFile.writeAsStringSync(pluginRegistryHeader);
|
||||
final File registryImplementationFile = registryDirectory.childFile('GeneratedPluginRegistrant.m');
|
||||
registryImplementationFile.writeAsStringSync(pluginRegistryImplementation);
|
||||
|
||||
}
|
||||
|
||||
class InjectPluginsResult{
|
||||
@ -224,17 +228,30 @@ class InjectPluginsResult{
|
||||
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;
|
||||
}
|
||||
|
||||
@ -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 @@ class FlutterProject {
|
||||
/// 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.
|
||||
|
||||
@ -21,6 +21,7 @@ import '../device.dart';
|
||||
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 @@ abstract class FlutterCommand extends Command<Null> {
|
||||
if (shouldUpdateCache)
|
||||
await cache.updateAll();
|
||||
|
||||
if (shouldRunPub)
|
||||
if (shouldRunPub) {
|
||||
await pubGet(context: PubContext.getVerifyContext(name));
|
||||
new FlutterProject(fs.currentDirectory).ensureReadyForPlatformSpecificTooling();
|
||||
}
|
||||
|
||||
setupApplicationPackages();
|
||||
|
||||
|
||||
@ -29,9 +29,19 @@ void main() {
|
||||
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,138 @@ void main() {
|
||||
commandArgs.add(projectPath);
|
||||
|
||||
await runner.run(commandArgs);
|
||||
|
||||
return projectPath;
|
||||
}
|
||||
|
||||
void expectExists(String projectPath, String relPath) {
|
||||
expect(fs.isFileSync(fs.path.join(projectPath, relPath)), true);
|
||||
}
|
||||
|
||||
// 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),
|
||||
);
|
||||
}
|
||||
|
||||
void expectNotExists(String projectPath, String relPath) {
|
||||
expect(fs.isFileSync(fs.path.join(projectPath, relPath)), false);
|
||||
}
|
||||
|
||||
void expectNotContains(String projectPath, String relPath, String substring) {
|
||||
expectExists(projectPath, relPath);
|
||||
expect(
|
||||
fs.file(fs.path.join(projectPath, relPath)).readAsStringSync(),
|
||||
isNot(contains(substring)),
|
||||
);
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
|
||||
@ -6,9 +6,11 @@ import 'dart:async';
|
||||
|
||||
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 @@ import '../src/context.dart';
|
||||
void main() {
|
||||
FileSystem fs;
|
||||
ProcessManager mockProcessManager;
|
||||
MockXcodeProjectInterpreter mockXcodeProjectInterpreter;
|
||||
Directory projectUnderTest;
|
||||
CocoaPods cocoaPodsUnderTest;
|
||||
|
||||
@ -25,7 +28,9 @@ void main() {
|
||||
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 @@ void main() {
|
||||
)).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 @@ void main() {
|
||||
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 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by
|
||||
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]);
|
||||
|
||||
@ -17,6 +17,29 @@ void main() {
|
||||
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 @@ void main() {
|
||||
});
|
||||
}
|
||||
|
||||
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 testInMemory(String description, Future<Null> testMethod()) {
|
||||
);
|
||||
}
|
||||
|
||||
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')
|
||||
|
||||
@ -19,6 +19,7 @@ import 'package:flutter_tools/src/device.dart';
|
||||
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 @@ void _defaultInitializeContext(AppContext testContext) {
|
||||
..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 @@ class MockUsage implements Usage {
|
||||
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 {}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user