Unverified Commit 3ebebebb authored by stuartmorgan's avatar stuartmorgan Committed by GitHub

Implement plugin tooling support for macOS (#33636)

Enables the CocoaPods-based plugin workflow for macOS. This allows a
macOS project to automatically fetch and add native plugin
implementations via CocoaPods for anything in pubspec.yaml, as is done
on iOS.
parent 27876e09
...@@ -10,7 +10,6 @@ import '../application_package.dart'; ...@@ -10,7 +10,6 @@ import '../application_package.dart';
import '../base/common.dart'; import '../base/common.dart';
import '../base/context.dart'; import '../base/context.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../base/fingerprint.dart';
import '../base/io.dart'; import '../base/io.dart';
import '../base/logger.dart'; import '../base/logger.dart';
import '../base/os.dart'; import '../base/os.dart';
...@@ -21,9 +20,8 @@ import '../base/utils.dart'; ...@@ -21,9 +20,8 @@ import '../base/utils.dart';
import '../build_info.dart'; import '../build_info.dart';
import '../convert.dart'; import '../convert.dart';
import '../globals.dart'; import '../globals.dart';
import '../macos/cocoapods.dart'; import '../macos/cocoapod_utils.dart';
import '../macos/xcode.dart'; import '../macos/xcode.dart';
import '../plugins.dart';
import '../project.dart'; import '../project.dart';
import '../services.dart'; import '../services.dart';
import 'code_signing.dart'; import 'code_signing.dart';
...@@ -274,29 +272,7 @@ Future<XcodeBuildResult> buildXcodeProject({ ...@@ -274,29 +272,7 @@ Future<XcodeBuildResult> buildXcodeProject({
targetOverride: targetOverride, targetOverride: targetOverride,
buildInfo: buildInfo, buildInfo: buildInfo,
); );
refreshPluginsList(project); await processPodsIfNeeded(project.ios, getIosBuildDirectory(), buildInfo.mode);
if (hasPlugins(project) || (project.isModule && project.ios.podfile.existsSync())) {
// If the Xcode project, Podfile, or Generated.xcconfig have changed since
// last run, pods should be updated.
final Fingerprinter fingerprinter = Fingerprinter(
fingerprintPath: fs.path.join(getIosBuildDirectory(), 'pod_inputs.fingerprint'),
paths: <String>[
app.project.xcodeProjectInfoFile.path,
app.project.podfile.path,
app.project.generatedXcodePropertiesFile.path,
],
properties: <String, String>{},
);
final bool didPodInstall = await cocoaPods.processPods(
iosProject: project.ios,
iosEngineDir: flutterFrameworkDir(buildInfo.mode),
isSwift: project.ios.isSwift,
dependenciesChanged: !await fingerprinter.doesFingerprintMatch(),
);
if (didPodInstall)
await fingerprinter.writeFingerprint();
}
final List<String> buildCommands = <String>[ final List<String> buildCommands = <String>[
'/usr/bin/env', '/usr/bin/env',
......
...@@ -12,6 +12,7 @@ import '../convert.dart'; ...@@ -12,6 +12,7 @@ import '../convert.dart';
import '../globals.dart'; import '../globals.dart';
import '../ios/xcodeproj.dart'; import '../ios/xcodeproj.dart';
import '../project.dart'; import '../project.dart';
import 'cocoapod_utils.dart';
/// Builds the macOS project through xcode build. /// Builds the macOS project through xcode build.
// TODO(jonahwilliams): support target option. // TODO(jonahwilliams): support target option.
...@@ -28,6 +29,8 @@ Future<void> buildMacOS(FlutterProject flutterProject, BuildInfo buildInfo) asyn ...@@ -28,6 +29,8 @@ Future<void> buildMacOS(FlutterProject flutterProject, BuildInfo buildInfo) asyn
useMacOSConfig: true, useMacOSConfig: true,
setSymroot: false, setSymroot: false,
); );
await processPodsIfNeeded(flutterProject.macos, getMacOSBuildDirectory(), buildInfo.mode);
// Set debug or release mode. // Set debug or release mode.
String config = 'Debug'; String config = 'Debug';
if (buildInfo.isRelease) { if (buildInfo.isRelease) {
......
// Copyright 2019 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 '../base/file_system.dart';
import '../base/fingerprint.dart';
import '../build_info.dart';
import '../ios/xcodeproj.dart';
import '../plugins.dart';
import '../project.dart';
import 'cocoapods.dart';
/// For a given build, determines whether dependencies have changed since the
/// last call to processPods, then calls processPods with that information.
Future<void> processPodsIfNeeded(XcodeBasedProject xcodeProject,
String buildDirectory, BuildMode buildMode) async {
final FlutterProject project = xcodeProject.parent;
// Ensure that the plugin list is up to date, since hasPlugins relies on it.
refreshPluginsList(project);
if (!(hasPlugins(project) || (project.isModule && xcodeProject.podfile.existsSync()))) {
return;
}
// If the Xcode project, Podfile, or generated xcconfig have changed since
// last run, pods should be updated.
final Fingerprinter fingerprinter = Fingerprinter(
fingerprintPath: fs.path.join(buildDirectory, 'pod_inputs.fingerprint'),
paths: <String>[
xcodeProject.xcodeProjectInfoFile.path,
xcodeProject.podfile.path,
xcodeProject.generatedXcodePropertiesFile.path,
],
properties: <String, String>{},
);
final bool didPodInstall = await cocoaPods.processPods(
xcodeProject: xcodeProject,
engineDir: flutterFrameworkDir(buildMode),
isSwift: xcodeProject.isSwift,
dependenciesChanged: !await fingerprinter.doesFingerprintMatch(),
);
if (didPodInstall) {
await fingerprinter.writeFingerprint();
}
}
...@@ -100,18 +100,18 @@ class CocoaPods { ...@@ -100,18 +100,18 @@ class CocoaPods {
} }
Future<bool> processPods({ Future<bool> processPods({
@required IosProject iosProject, @required XcodeBasedProject xcodeProject,
// For backward compatibility with previously created Podfile only. // For backward compatibility with previously created Podfile only.
@required String iosEngineDir, @required String engineDir,
bool isSwift = false, bool isSwift = false,
bool dependenciesChanged = true, bool dependenciesChanged = true,
}) async { }) async {
if (!(await iosProject.podfile.exists())) { if (!(await xcodeProject.podfile.exists())) {
throwToolExit('Podfile missing'); throwToolExit('Podfile missing');
} }
if (await _checkPodCondition()) { if (await _checkPodCondition()) {
if (_shouldRunPodInstall(iosProject, dependenciesChanged)) { if (_shouldRunPodInstall(xcodeProject, dependenciesChanged)) {
await _runPodInstall(iosProject, iosEngineDir); await _runPodInstall(xcodeProject, engineDir);
return true; return true;
} }
} }
...@@ -176,46 +176,52 @@ class CocoaPods { ...@@ -176,46 +176,52 @@ class CocoaPods {
return true; return true;
} }
/// Ensures the given iOS sub-project of a parent Flutter project /// Ensures the given Xcode-based sub-project of a parent Flutter project
/// contains a suitable `Podfile` and that its `Flutter/Xxx.xcconfig` files /// contains a suitable `Podfile` and that its `Flutter/Xxx.xcconfig` files
/// include pods configuration. /// include pods configuration.
void setupPodfile(IosProject iosProject) { void setupPodfile(XcodeBasedProject xcodeProject) {
if (!xcodeProjectInterpreter.isInstalled) { if (!xcodeProjectInterpreter.isInstalled) {
// Don't do anything for iOS when host platform doesn't support it. // Don't do anything for iOS when host platform doesn't support it.
return; return;
} }
final Directory runnerProject = iosProject.xcodeProject; final Directory runnerProject = xcodeProject.xcodeProject;
if (!runnerProject.existsSync()) { if (!runnerProject.existsSync()) {
return; return;
} }
final File podfile = iosProject.podfile; final File podfile = xcodeProject.podfile;
if (!podfile.existsSync()) { if (!podfile.existsSync()) {
final bool isSwift = xcodeProjectInterpreter.getBuildSettings( String podfileTemplateName;
runnerProject.path, if (xcodeProject is MacOSProject) {
'Runner', podfileTemplateName = 'Podfile-macos';
).containsKey('SWIFT_VERSION'); } else {
final bool isSwift = xcodeProjectInterpreter.getBuildSettings(
runnerProject.path,
'Runner',
).containsKey('SWIFT_VERSION');
podfileTemplateName = isSwift ? 'Podfile-ios-swift' : 'Podfile-ios-objc';
}
final File podfileTemplate = fs.file(fs.path.join( final File podfileTemplate = fs.file(fs.path.join(
Cache.flutterRoot, Cache.flutterRoot,
'packages', 'packages',
'flutter_tools', 'flutter_tools',
'templates', 'templates',
'cocoapods', 'cocoapods',
isSwift ? 'Podfile-swift' : 'Podfile-objc', podfileTemplateName,
)); ));
podfileTemplate.copySync(podfile.path); podfileTemplate.copySync(podfile.path);
} }
addPodsDependencyToFlutterXcconfig(iosProject); addPodsDependencyToFlutterXcconfig(xcodeProject);
} }
/// Ensures all `Flutter/Xxx.xcconfig` files for the given iOS sub-project of /// Ensures all `Flutter/Xxx.xcconfig` files for the given Xcode-based
/// a parent Flutter project include pods configuration. /// sub-project of a parent Flutter project include pods configuration.
void addPodsDependencyToFlutterXcconfig(IosProject iosProject) { void addPodsDependencyToFlutterXcconfig(XcodeBasedProject xcodeProject) {
_addPodsDependencyToFlutterXcconfig(iosProject, 'Debug'); _addPodsDependencyToFlutterXcconfig(xcodeProject, 'Debug');
_addPodsDependencyToFlutterXcconfig(iosProject, 'Release'); _addPodsDependencyToFlutterXcconfig(xcodeProject, 'Release');
} }
void _addPodsDependencyToFlutterXcconfig(IosProject iosProject, String mode) { void _addPodsDependencyToFlutterXcconfig(XcodeBasedProject xcodeProject, String mode) {
final File file = iosProject.xcodeConfigFor(mode); final File file = xcodeProject.xcodeConfigFor(mode);
if (file.existsSync()) { if (file.existsSync()) {
final String content = file.readAsStringSync(); final String content = file.readAsStringSync();
final String include = '#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.${mode final String include = '#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.${mode
...@@ -226,8 +232,8 @@ class CocoaPods { ...@@ -226,8 +232,8 @@ class CocoaPods {
} }
/// Ensures that pod install is deemed needed on next check. /// Ensures that pod install is deemed needed on next check.
void invalidatePodInstallOutput(IosProject iosProject) { void invalidatePodInstallOutput(XcodeBasedProject xcodeProject) {
final File manifestLock = iosProject.podManifestLock; final File manifestLock = xcodeProject.podManifestLock;
if (manifestLock.existsSync()) { if (manifestLock.existsSync()) {
manifestLock.deleteSync(); manifestLock.deleteSync();
} }
...@@ -239,13 +245,13 @@ class CocoaPods { ...@@ -239,13 +245,13 @@ class CocoaPods {
// 2. Podfile.lock doesn't exist or is older than Podfile // 2. Podfile.lock doesn't exist or is older than Podfile
// 3. Pods/Manifest.lock doesn't exist (It is deleted when plugins change) // 3. Pods/Manifest.lock doesn't exist (It is deleted when plugins change)
// 4. Podfile.lock doesn't match Pods/Manifest.lock. // 4. Podfile.lock doesn't match Pods/Manifest.lock.
bool _shouldRunPodInstall(IosProject iosProject, bool dependenciesChanged) { bool _shouldRunPodInstall(XcodeBasedProject xcodeProject, bool dependenciesChanged) {
if (dependenciesChanged) if (dependenciesChanged)
return true; return true;
final File podfileFile = iosProject.podfile; final File podfileFile = xcodeProject.podfile;
final File podfileLockFile = iosProject.podfileLock; final File podfileLockFile = xcodeProject.podfileLock;
final File manifestLockFile = iosProject.podManifestLock; final File manifestLockFile = xcodeProject.podManifestLock;
return !podfileLockFile.existsSync() return !podfileLockFile.existsSync()
|| !manifestLockFile.existsSync() || !manifestLockFile.existsSync()
...@@ -253,11 +259,11 @@ class CocoaPods { ...@@ -253,11 +259,11 @@ class CocoaPods {
|| podfileLockFile.readAsStringSync() != manifestLockFile.readAsStringSync(); || podfileLockFile.readAsStringSync() != manifestLockFile.readAsStringSync();
} }
Future<void> _runPodInstall(IosProject iosProject, String engineDirectory) async { Future<void> _runPodInstall(XcodeBasedProject xcodeProject, String engineDirectory) async {
final Status status = logger.startProgress('Running pod install...', timeout: timeoutConfiguration.slowOperation); final Status status = logger.startProgress('Running pod install...', timeout: timeoutConfiguration.slowOperation);
final ProcessResult result = await processManager.run( final ProcessResult result = await processManager.run(
<String>['pod', 'install', '--verbose'], <String>['pod', 'install', '--verbose'],
workingDirectory: iosProject.hostAppRoot.path, workingDirectory: fs.path.dirname(xcodeProject.podfile.path),
environment: <String, String>{ environment: <String, String>{
// For backward compatibility with previously created Podfile only. // For backward compatibility with previously created Podfile only.
'FLUTTER_FRAMEWORK_DIR': engineDirectory, 'FLUTTER_FRAMEWORK_DIR': engineDirectory,
...@@ -278,7 +284,7 @@ class CocoaPods { ...@@ -278,7 +284,7 @@ class CocoaPods {
} }
} }
if (result.exitCode != 0) { if (result.exitCode != 0) {
invalidatePodInstallOutput(iosProject); invalidatePodInstallOutput(xcodeProject);
_diagnosePodInstallFailure(result); _diagnosePodInstallFailure(result);
throwToolExit('Error running pod install'); throwToolExit('Error running pod install');
} }
......
...@@ -9,6 +9,7 @@ import 'package:yaml/yaml.dart'; ...@@ -9,6 +9,7 @@ import 'package:yaml/yaml.dart';
import 'base/file_system.dart'; import 'base/file_system.dart';
import 'dart/package_map.dart'; import 'dart/package_map.dart';
import 'desktop.dart';
import 'globals.dart'; import 'globals.dart';
import 'macos/cocoapods.dart'; import 'macos/cocoapods.dart';
import 'project.dart'; import 'project.dart';
...@@ -39,7 +40,9 @@ class Plugin { ...@@ -39,7 +40,9 @@ class Plugin {
if (pluginYaml != null) { if (pluginYaml != null) {
androidPackage = pluginYaml['androidPackage']; androidPackage = pluginYaml['androidPackage'];
iosPrefix = pluginYaml['iosPrefix'] ?? ''; iosPrefix = pluginYaml['iosPrefix'] ?? '';
macosPrefix = pluginYaml['macosPrefix'] ?? ''; // TODO(stuartmorgan): Add |?? ''| here as well once this isn't used as
// an indicator of macOS support, see https://github.com/flutter/flutter/issues/33597
macosPrefix = pluginYaml['macosPrefix'];
pluginClass = pluginYaml['pluginClass']; pluginClass = pluginYaml['pluginClass'];
} }
return Plugin( return Plugin(
...@@ -179,14 +182,14 @@ Future<void> _writeAndroidPluginRegistrant(FlutterProject project, List<Plugin> ...@@ -179,14 +182,14 @@ Future<void> _writeAndroidPluginRegistrant(FlutterProject project, List<Plugin>
_renderTemplateToFile(_androidPluginRegistryTemplate, context, registryPath); _renderTemplateToFile(_androidPluginRegistryTemplate, context, registryPath);
} }
const String _iosPluginRegistryHeaderTemplate = '''// const String _cocoaPluginRegistryHeaderTemplate = '''//
// Generated file. Do not edit. // Generated file. Do not edit.
// //
#ifndef GeneratedPluginRegistrant_h #ifndef GeneratedPluginRegistrant_h
#define GeneratedPluginRegistrant_h #define GeneratedPluginRegistrant_h
#import <Flutter/Flutter.h> #import <{{framework}}/{{framework}}.h>
@interface GeneratedPluginRegistrant : NSObject @interface GeneratedPluginRegistrant : NSObject
+ (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry; + (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry;
...@@ -195,7 +198,7 @@ const String _iosPluginRegistryHeaderTemplate = '''// ...@@ -195,7 +198,7 @@ const String _iosPluginRegistryHeaderTemplate = '''//
#endif /* GeneratedPluginRegistrant_h */ #endif /* GeneratedPluginRegistrant_h */
'''; ''';
const String _iosPluginRegistryImplementationTemplate = '''// const String _cocoaPluginRegistryImplementationTemplate = '''//
// Generated file. Do not edit. // Generated file. Do not edit.
// //
...@@ -215,7 +218,7 @@ const String _iosPluginRegistryImplementationTemplate = '''// ...@@ -215,7 +218,7 @@ const String _iosPluginRegistryImplementationTemplate = '''//
@end @end
'''; ''';
const String _iosPluginRegistrantPodspecTemplate = ''' const String _pluginRegistrantPodspecTemplate = '''
# #
# Generated file, do not edit. # Generated file, do not edit.
# #
...@@ -230,11 +233,11 @@ Depends on all your plugins, and provides a function to register them. ...@@ -230,11 +233,11 @@ Depends on all your plugins, and provides a function to register them.
s.homepage = 'https://flutter.dev' s.homepage = 'https://flutter.dev'
s.license = { :type => 'BSD' } s.license = { :type => 'BSD' }
s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' }
s.ios.deployment_target = '8.0' s.{{os}}.deployment_target = '{{deploymentTarget}}'
s.source_files = "Classes", "Classes/**/*.{h,m}" s.source_files = "Classes", "Classes/**/*.{h,m}"
s.source = { :path => '.' } s.source = { :path => '.' }
s.public_header_files = './Classes/**/*.h' s.public_header_files = './Classes/**/*.h'
s.dependency 'Flutter' s.dependency '{{framework}}'
{{#plugins}} {{#plugins}}
s.dependency '{{name}}' s.dependency '{{name}}'
{{/plugins}} {{/plugins}}
...@@ -250,36 +253,64 @@ Future<void> _writeIOSPluginRegistrant(FlutterProject project, List<Plugin> plug ...@@ -250,36 +253,64 @@ Future<void> _writeIOSPluginRegistrant(FlutterProject project, List<Plugin> plug
'class': p.pluginClass, 'class': p.pluginClass,
}).toList(); }).toList();
final Map<String, dynamic> context = <String, dynamic>{ final Map<String, dynamic> context = <String, dynamic>{
'os': 'ios',
'deploymentTarget': '8.0',
'framework': 'Flutter',
'plugins': iosPlugins, 'plugins': iosPlugins,
}; };
final String registryDirectory = project.ios.pluginRegistrantHost.path; final String registryDirectory = project.ios.pluginRegistrantHost.path;
return await _writeCocoaPluginRegistrant(project, context, registryDirectory);
}
Future<void> _writeMacOSPluginRegistrant(FlutterProject project, List<Plugin> plugins) async {
// TODO(stuartmorgan): Replace macosPrefix check with formal metadata check,
// see https://github.com/flutter/flutter/issues/33597.
final List<Map<String, dynamic>> macosPlugins = plugins
.where((Plugin p) => p.pluginClass != null && p.macosPrefix != null)
.map<Map<String, dynamic>>((Plugin p) => <String, dynamic>{
'name': p.name,
'prefix': p.macosPrefix,
'class': p.pluginClass,
}).toList();
final Map<String, dynamic> context = <String, dynamic>{
'os': 'macos',
'deploymentTarget': '10.13',
'framework': 'FlutterMacOS',
'plugins': macosPlugins,
};
final String registryDirectory = project.macos.managedDirectory.path;
return await _writeCocoaPluginRegistrant(project, context, registryDirectory);
}
Future<void> _writeCocoaPluginRegistrant(FlutterProject project,
Map<String, dynamic> templateContext, String registryDirectory) async {
if (project.isModule) { if (project.isModule) {
final String registryClassesDirectory = fs.path.join(registryDirectory, 'Classes'); final String registryClassesDirectory = fs.path.join(registryDirectory, 'Classes');
_renderTemplateToFile( _renderTemplateToFile(
_iosPluginRegistrantPodspecTemplate, _pluginRegistrantPodspecTemplate,
context, templateContext,
fs.path.join(registryDirectory, 'FlutterPluginRegistrant.podspec'), fs.path.join(registryDirectory, 'FlutterPluginRegistrant.podspec'),
); );
_renderTemplateToFile( _renderTemplateToFile(
_iosPluginRegistryHeaderTemplate, _cocoaPluginRegistryHeaderTemplate,
context, templateContext,
fs.path.join(registryClassesDirectory, 'GeneratedPluginRegistrant.h'), fs.path.join(registryClassesDirectory, 'GeneratedPluginRegistrant.h'),
); );
_renderTemplateToFile( _renderTemplateToFile(
_iosPluginRegistryImplementationTemplate, _cocoaPluginRegistryImplementationTemplate,
context, templateContext,
fs.path.join(registryClassesDirectory, 'GeneratedPluginRegistrant.m'), fs.path.join(registryClassesDirectory, 'GeneratedPluginRegistrant.m'),
); );
} else { } else {
_renderTemplateToFile( _renderTemplateToFile(
_iosPluginRegistryHeaderTemplate, _cocoaPluginRegistryHeaderTemplate,
context, templateContext,
fs.path.join(registryDirectory, 'GeneratedPluginRegistrant.h'), fs.path.join(registryDirectory, 'GeneratedPluginRegistrant.h'),
); );
_renderTemplateToFile( _renderTemplateToFile(
_iosPluginRegistryImplementationTemplate, _cocoaPluginRegistryImplementationTemplate,
context, templateContext,
fs.path.join(registryDirectory, 'GeneratedPluginRegistrant.m'), fs.path.join(registryDirectory, 'GeneratedPluginRegistrant.m'),
); );
} }
...@@ -317,17 +348,25 @@ Future<void> injectPlugins(FlutterProject project, {bool checkProjects = false}) ...@@ -317,17 +348,25 @@ Future<void> injectPlugins(FlutterProject project, {bool checkProjects = false})
if ((checkProjects && project.ios.existsSync()) || !checkProjects) { if ((checkProjects && project.ios.existsSync()) || !checkProjects) {
await _writeIOSPluginRegistrant(project, plugins); await _writeIOSPluginRegistrant(project, plugins);
} }
if (!project.isModule && ((project.ios.hostAppRoot.existsSync() && checkProjects) || !checkProjects)) { // TODO(stuartmorgan): Revisit the condition here once the plans for handling
// desktop in existing projects are in place. For now, ignore checkProjects
// on desktop and always treat it as true.
if (flutterDesktopEnabled && project.macos.existsSync()) {
await _writeMacOSPluginRegistrant(project, plugins);
}
for (final XcodeBasedProject subproject in <XcodeBasedProject>[project.ios, project.macos]) {
if (!project.isModule && (!checkProjects || subproject.existsSync())) {
final CocoaPods cocoaPods = CocoaPods(); final CocoaPods cocoaPods = CocoaPods();
if (plugins.isNotEmpty) { if (plugins.isNotEmpty) {
cocoaPods.setupPodfile(project.ios); cocoaPods.setupPodfile(subproject);
} }
/// The user may have a custom maintained Podfile that they're running `pod install` /// The user may have a custom maintained Podfile that they're running `pod install`
/// on themselves. /// on themselves.
else if (project.ios.podfile.existsSync() && project.ios.podfileLock.existsSync()) { else if (subproject.podfile.existsSync() && subproject.podfileLock.existsSync()) {
cocoaPods.addPodsDependencyToFlutterXcconfig(project.ios); cocoaPods.addPodsDependencyToFlutterXcconfig(subproject);
} }
} }
}
} }
/// Returns whether the specified Flutter [project] has any plugin dependencies. /// Returns whether the specified Flutter [project] has any plugin dependencies.
......
...@@ -13,6 +13,7 @@ import 'base/file_system.dart'; ...@@ -13,6 +13,7 @@ import 'base/file_system.dart';
import 'build_info.dart'; import 'build_info.dart';
import 'bundle.dart' as bundle; import 'bundle.dart' as bundle;
import 'cache.dart'; import 'cache.dart';
import 'desktop.dart';
import 'flutter_manifest.dart'; import 'flutter_manifest.dart';
import 'ios/ios_workflow.dart'; import 'ios/ios_workflow.dart';
import 'ios/plist_utils.dart' as plist; import 'ios/plist_utils.dart' as plist;
...@@ -179,6 +180,11 @@ class FlutterProject { ...@@ -179,6 +180,11 @@ class FlutterProject {
if ((ios.existsSync() && checkProjects) || !checkProjects) { if ((ios.existsSync() && checkProjects) || !checkProjects) {
await ios.ensureReadyForPlatformSpecificTooling(); await ios.ensureReadyForPlatformSpecificTooling();
} }
// TODO(stuartmorgan): Add checkProjects logic once a create workflow exists
// for macOS. For now, always treat checkProjects as true for macOS.
if (flutterDesktopEnabled && macos.existsSync()) {
await macos.ensureReadyForPlatformSpecificTooling();
}
if (flutterWebEnabled) { if (flutterWebEnabled) {
await web.ensureReadyForPlatformSpecificTooling(); await web.ensureReadyForPlatformSpecificTooling();
} }
...@@ -205,14 +211,53 @@ class FlutterProject { ...@@ -205,14 +211,53 @@ class FlutterProject {
} }
} }
/// Represents an Xcode-based sub-project.
///
/// This defines interfaces common to iOS and macOS projects.
abstract class XcodeBasedProject {
/// The parent of this project.
FlutterProject get parent;
/// Whether the subproject (either iOS or macOS) exists in the Flutter project.
bool existsSync();
/// The Xcode project (.xcodeproj directory) of the host app.
Directory get xcodeProject;
/// The 'project.pbxproj' file of [xcodeProject].
File get xcodeProjectInfoFile;
/// The Xcode workspace (.xcworkspace directory) of the host app.
Directory get xcodeWorkspace;
/// Contains definitions for FLUTTER_ROOT, LOCAL_ENGINE, and more flags for
/// the Xcode build.
File get generatedXcodePropertiesFile;
/// The Flutter-managed Xcode config file for [mode].
File xcodeConfigFor(String mode);
/// The CocoaPods 'Podfile'.
File get podfile;
/// The CocoaPods 'Podfile.lock'.
File get podfileLock;
/// The CocoaPods 'Manifest.lock'.
File get podManifestLock;
/// True if the host app project is using Swift.
bool get isSwift;
}
/// Represents the iOS sub-project of a Flutter project. /// Represents the iOS sub-project of a Flutter project.
/// ///
/// Instances will reflect the contents of the `ios/` sub-folder of /// Instances will reflect the contents of the `ios/` sub-folder of
/// Flutter applications and the `.ios/` sub-folder of Flutter module projects. /// Flutter applications and the `.ios/` sub-folder of Flutter module projects.
class IosProject { class IosProject implements XcodeBasedProject {
IosProject.fromFlutter(this.parent); IosProject.fromFlutter(this.parent);
/// The parent of this project. @override
final FlutterProject parent; final FlutterProject parent;
static final RegExp _productBundleIdPattern = RegExp(r'''^\s*PRODUCT_BUNDLE_IDENTIFIER\s*=\s*(["']?)(.*?)\1;\s*$'''); static final RegExp _productBundleIdPattern = RegExp(r'''^\s*PRODUCT_BUNDLE_IDENTIFIER\s*=\s*(["']?)(.*?)\1;\s*$''');
...@@ -246,28 +291,28 @@ class IosProject { ...@@ -246,28 +291,28 @@ class IosProject {
/// Whether the flutter application has an iOS project. /// Whether the flutter application has an iOS project.
bool get exists => hostAppRoot.existsSync(); bool get exists => hostAppRoot.existsSync();
/// The xcode config file for [mode]. @override
File xcodeConfigFor(String mode) => _flutterLibRoot.childDirectory('Flutter').childFile('$mode.xcconfig'); File xcodeConfigFor(String mode) => _flutterLibRoot.childDirectory('Flutter').childFile('$mode.xcconfig');
/// The 'Podfile'. @override
File get podfile => hostAppRoot.childFile('Podfile'); File get podfile => hostAppRoot.childFile('Podfile');
/// The 'Podfile.lock'. @override
File get podfileLock => hostAppRoot.childFile('Podfile.lock'); File get podfileLock => hostAppRoot.childFile('Podfile.lock');
/// The 'Manifest.lock'. @override
File get podManifestLock => hostAppRoot.childDirectory('Pods').childFile('Manifest.lock'); File get podManifestLock => hostAppRoot.childDirectory('Pods').childFile('Manifest.lock');
/// The 'Info.plist' file of the host app. /// The 'Info.plist' file of the host app.
File get hostInfoPlist => hostAppRoot.childDirectory(_hostAppBundleName).childFile('Info.plist'); File get hostInfoPlist => hostAppRoot.childDirectory(_hostAppBundleName).childFile('Info.plist');
/// '.xcodeproj' folder of the host app. @override
Directory get xcodeProject => hostAppRoot.childDirectory('$_hostAppBundleName.xcodeproj'); Directory get xcodeProject => hostAppRoot.childDirectory('$_hostAppBundleName.xcodeproj');
/// The '.pbxproj' file of the host app. @override
File get xcodeProjectInfoFile => xcodeProject.childFile('project.pbxproj'); File get xcodeProjectInfoFile => xcodeProject.childFile('project.pbxproj');
/// Xcode workspace directory of the host app. @override
Directory get xcodeWorkspace => hostAppRoot.childDirectory('$_hostAppBundleName.xcworkspace'); Directory get xcodeWorkspace => hostAppRoot.childDirectory('$_hostAppBundleName.xcworkspace');
/// Xcode workspace shared data directory for the host app. /// Xcode workspace shared data directory for the host app.
...@@ -276,7 +321,7 @@ class IosProject { ...@@ -276,7 +321,7 @@ class IosProject {
/// Xcode workspace shared workspace settings file for the host app. /// Xcode workspace shared workspace settings file for the host app.
File get xcodeWorkspaceSharedSettings => xcodeWorkspaceSharedData.childFile('WorkspaceSettings.xcsettings'); File get xcodeWorkspaceSharedSettings => xcodeWorkspaceSharedData.childFile('WorkspaceSettings.xcsettings');
/// Whether the current flutter project has an iOS subproject. @override
bool existsSync() { bool existsSync() {
return parent.isModule || _editableDirectory.existsSync(); return parent.isModule || _editableDirectory.existsSync();
} }
...@@ -304,7 +349,7 @@ class IosProject { ...@@ -304,7 +349,7 @@ class IosProject {
return null; return null;
} }
/// True, if the host app project is using Swift. @override
bool get isSwift => buildSettings?.containsKey('SWIFT_VERSION') ?? false; bool get isSwift => buildSettings?.containsKey('SWIFT_VERSION') ?? false;
/// The build settings for the host app of this project, as a detached map. /// The build settings for the host app of this project, as a detached map.
...@@ -364,6 +409,7 @@ class IosProject { ...@@ -364,6 +409,7 @@ class IosProject {
await injectPlugins(parent); await injectPlugins(parent);
} }
@override
File get generatedXcodePropertiesFile => _flutterLibRoot.childDirectory('Flutter').childFile('Generated.xcconfig'); File get generatedXcodePropertiesFile => _flutterLibRoot.childDirectory('Flutter').childFile('Generated.xcconfig');
Directory get pluginRegistrantHost { Directory get pluginRegistrantHost {
...@@ -573,16 +619,18 @@ Match _firstMatchInFile(File file, RegExp regExp) { ...@@ -573,16 +619,18 @@ Match _firstMatchInFile(File file, RegExp regExp) {
} }
/// The macOS sub project. /// The macOS sub project.
class MacOSProject { class MacOSProject implements XcodeBasedProject {
MacOSProject._(this.project); MacOSProject._(this.parent);
final FlutterProject project; @override
final FlutterProject parent;
static const String _hostAppBundleName = 'Runner'; static const String _hostAppBundleName = 'Runner';
@override
bool existsSync() => _macOSDirectory.existsSync(); bool existsSync() => _macOSDirectory.existsSync();
Directory get _macOSDirectory => project.directory.childDirectory('macos'); Directory get _macOSDirectory => parent.directory.childDirectory('macos');
/// The directory in the project that is managed by Flutter. As much as /// The directory in the project that is managed by Flutter. As much as
/// possible, files that are edited by Flutter tooling after initial project /// possible, files that are edited by Flutter tooling after initial project
...@@ -594,24 +642,54 @@ class MacOSProject { ...@@ -594,24 +642,54 @@ class MacOSProject {
/// checked in should live here. /// checked in should live here.
Directory get ephemeralDirectory => managedDirectory.childDirectory('ephemeral'); Directory get ephemeralDirectory => managedDirectory.childDirectory('ephemeral');
/// Contains definitions for FLUTTER_ROOT, LOCAL_ENGINE, and more flags for @override
/// the Xcode build.
File get generatedXcodePropertiesFile => ephemeralDirectory.childFile('Flutter-Generated.xcconfig'); File get generatedXcodePropertiesFile => ephemeralDirectory.childFile('Flutter-Generated.xcconfig');
/// The Flutter-managed Xcode config file for [mode]. @override
File xcodeConfigFor(String mode) => managedDirectory.childFile('Flutter-$mode.xcconfig'); File xcodeConfigFor(String mode) => managedDirectory.childFile('Flutter-$mode.xcconfig');
/// The Xcode project file. @override
File get podfile => _macOSDirectory.childFile('Podfile');
@override
File get podfileLock => _macOSDirectory.childFile('Podfile.lock');
@override
File get podManifestLock => _macOSDirectory.childDirectory('Pods').childFile('Manifest.lock');
@override
Directory get xcodeProject => _macOSDirectory.childDirectory('$_hostAppBundleName.xcodeproj'); Directory get xcodeProject => _macOSDirectory.childDirectory('$_hostAppBundleName.xcodeproj');
/// The Xcode workspace file. @override
File get xcodeProjectInfoFile => xcodeProject.childFile('project.pbxproj');
@override
Directory get xcodeWorkspace => _macOSDirectory.childDirectory('$_hostAppBundleName.xcworkspace'); Directory get xcodeWorkspace => _macOSDirectory.childDirectory('$_hostAppBundleName.xcworkspace');
@override
bool get isSwift => true;
/// The file where the Xcode build will write the name of the built app. /// The file where the Xcode build will write the name of the built app.
/// ///
/// Ideally this will be replaced in the future with inspection of the Runner /// Ideally this will be replaced in the future with inspection of the Runner
/// scheme's target. /// scheme's target.
File get nameFile => ephemeralDirectory.childFile('.app_filename'); File get nameFile => ephemeralDirectory.childFile('.app_filename');
Future<void> ensureReadyForPlatformSpecificTooling() async {
// TODO(stuartmorgan): Add create-from-template logic here.
await _updateGeneratedXcodeConfigIfNeeded();
}
Future<void> _updateGeneratedXcodeConfigIfNeeded() async {
if (Cache.instance.isOlderThanToolsStamp(generatedXcodePropertiesFile)) {
await xcode.updateGeneratedXcodeProperties(
project: parent,
buildInfo: BuildInfo.debug,
useMacOSConfig: true,
setSymroot: false,
);
}
}
} }
/// The Windows sub project /// The Windows sub project
......
platform :osx, '10.13'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
# TODO: Add Profile support to projects.
#'Profile' => :release,
'Release' => :release,
}
def parse_KV_file(file, separator='=')
file_abs_path = File.expand_path(file)
if !File.exists? file_abs_path
return [];
end
pods_ary = []
skip_line_start_symbols = ["#", "/"]
File.foreach(file_abs_path) { |line|
next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ }
plugin = line.split(pattern=separator)
if plugin.length == 2
podname = plugin[0].strip()
path = plugin[1].strip()
podpath = File.expand_path("#{path}", file_abs_path)
pods_ary.push({:name => podname, :path => podpath});
else
puts "Invalid plugin specification: #{line}"
end
}
return pods_ary
end
def pubspec_supports_macos(file)
file_abs_path = File.expand_path(file)
if !File.exists? file_abs_path
return false;
end
File.foreach(file_abs_path) { |line|
# TODO(stuartmorgan): Use formal platform declaration once it exists,
# see https://github.com/flutter/flutter/issues/33597.
return true if line =~ /^\s*macosPrefix:/
}
return false
end
target 'Runner' do
# Prepare symlinks folder. We use symlinks to avoid having Podfile.lock
# referring to absolute paths on developers' machines.
ephemeral_dir = File.join('Flutter', 'ephemeral')
symlink_dir = File.join(ephemeral_dir, '.symlinks')
symlink_plugins_dir = File.join(symlink_dir, 'plugins')
system("rm -rf #{symlink_dir}")
system("mkdir -p #{symlink_plugins_dir}")
# Flutter Pods
generated_xcconfig = parse_KV_file(File.join(ephemeral_dir, 'Flutter-Generated.xcconfig'))
if generated_xcconfig.empty?
puts "Flutter-Generated.xcconfig must exist. If you're running pod install manually, make sure flutter packages get is executed first."
end
generated_xcconfig.map { |p|
if p[:name] == 'FLUTTER_FRAMEWORK_DIR'
symlink = File.join(symlink_dir, 'flutter')
File.symlink(File.dirname(p[:path]), symlink)
pod 'FlutterMacOS', :path => File.join(symlink, File.basename(p[:path]))
end
}
# Plugin Pods
plugin_pods = parse_KV_file('../.flutter-plugins')
plugin_pods.map { |p|
symlink = File.join(symlink_plugins_dir, p[:name])
File.symlink(p[:path], symlink)
if pubspec_supports_macos(File.join(symlink, 'pubspec.yaml'))
pod p[:name], :path => File.join(symlink, 'macos')
end
}
end
...@@ -62,15 +62,20 @@ void main() { ...@@ -62,15 +62,20 @@ void main() {
cocoaPodsUnderTest = CocoaPods(); cocoaPodsUnderTest = CocoaPods();
pretendPodVersionIs('1.5.0'); pretendPodVersionIs('1.5.0');
fs.file(fs.path.join( fs.file(fs.path.join(
Cache.flutterRoot, 'packages', 'flutter_tools', 'templates', 'cocoapods', 'Podfile-objc', Cache.flutterRoot, 'packages', 'flutter_tools', 'templates', 'cocoapods', 'Podfile-ios-objc',
)) ))
..createSync(recursive: true) ..createSync(recursive: true)
..writeAsStringSync('Objective-C podfile template'); ..writeAsStringSync('Objective-C iOS podfile template');
fs.file(fs.path.join( fs.file(fs.path.join(
Cache.flutterRoot, 'packages', 'flutter_tools', 'templates', 'cocoapods', 'Podfile-swift', Cache.flutterRoot, 'packages', 'flutter_tools', 'templates', 'cocoapods', 'Podfile-ios-swift',
)) ))
..createSync(recursive: true) ..createSync(recursive: true)
..writeAsStringSync('Swift podfile template'); ..writeAsStringSync('Swift iOS podfile template');
fs.file(fs.path.join(
Cache.flutterRoot, 'packages', 'flutter_tools', 'templates', 'cocoapods', 'Podfile-macos',
))
..createSync(recursive: true)
..writeAsStringSync('macOS podfile template');
when(mockProcessManager.run( when(mockProcessManager.run(
<String>['pod', '--version'], <String>['pod', '--version'],
workingDirectory: anyNamed('workingDirectory'), workingDirectory: anyNamed('workingDirectory'),
...@@ -81,6 +86,11 @@ void main() { ...@@ -81,6 +86,11 @@ void main() {
workingDirectory: 'project/ios', 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'},
)).thenAnswer((_) async => exitsHappy()); )).thenAnswer((_) async => exitsHappy());
when(mockProcessManager.run(
<String>['pod', 'install', '--verbose'],
workingDirectory: 'project/macos',
environment: <String, String>{'FLUTTER_FRAMEWORK_DIR': 'engine/path', 'COCOAPODS_DISABLE_STATS': 'true'},
)).thenAnswer((_) async => exitsHappy());
}); });
group('Evaluate installation', () { group('Evaluate installation', () {
...@@ -145,7 +155,7 @@ void main() { ...@@ -145,7 +155,7 @@ void main() {
testUsingContext('creates objective-c Podfile when not present', () async { testUsingContext('creates objective-c Podfile when not present', () async {
cocoaPodsUnderTest.setupPodfile(projectUnderTest.ios); cocoaPodsUnderTest.setupPodfile(projectUnderTest.ios);
expect(projectUnderTest.ios.podfile.readAsStringSync(), 'Objective-C podfile template'); expect(projectUnderTest.ios.podfile.readAsStringSync(), 'Objective-C iOS podfile template');
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => fs, FileSystem: () => fs,
}); });
...@@ -159,12 +169,21 @@ void main() { ...@@ -159,12 +169,21 @@ void main() {
final FlutterProject project = FlutterProject.fromPath('project'); final FlutterProject project = FlutterProject.fromPath('project');
cocoaPodsUnderTest.setupPodfile(project.ios); cocoaPodsUnderTest.setupPodfile(project.ios);
expect(projectUnderTest.ios.podfile.readAsStringSync(), 'Swift podfile template'); expect(projectUnderTest.ios.podfile.readAsStringSync(), 'Swift iOS podfile template');
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => fs, FileSystem: () => fs,
XcodeProjectInterpreter: () => mockXcodeProjectInterpreter, XcodeProjectInterpreter: () => mockXcodeProjectInterpreter,
}); });
testUsingContext('creates macOS Podfile when not present', () async {
projectUnderTest.macos.xcodeProject.createSync(recursive: true);
cocoaPodsUnderTest.setupPodfile(projectUnderTest.macos);
expect(projectUnderTest.macos.podfile.readAsStringSync(), 'macOS podfile template');
}, overrides: <Type, Generator>{
FileSystem: () => fs,
});
testUsingContext('does not recreate Podfile when already present', () async { testUsingContext('does not recreate Podfile when already present', () async {
projectUnderTest.ios.podfile..createSync()..writeAsStringSync('Existing Podfile'); projectUnderTest.ios.podfile..createSync()..writeAsStringSync('Existing Podfile');
...@@ -250,8 +269,8 @@ void main() { ...@@ -250,8 +269,8 @@ void main() {
pretendPodIsNotInstalled(); pretendPodIsNotInstalled();
projectUnderTest.ios.podfile.createSync(); projectUnderTest.ios.podfile.createSync();
final bool didInstall = await cocoaPodsUnderTest.processPods( final bool didInstall = await cocoaPodsUnderTest.processPods(
iosProject: projectUnderTest.ios, xcodeProject: projectUnderTest.ios,
iosEngineDir: 'engine/path', engineDir: 'engine/path',
); );
verifyNever(mockProcessManager.run( verifyNever(mockProcessManager.run(
argThat(containsAllInOrder(<String>['pod', 'install'])), argThat(containsAllInOrder(<String>['pod', 'install'])),
...@@ -269,8 +288,8 @@ void main() { ...@@ -269,8 +288,8 @@ void main() {
testUsingContext('throws, if Podfile is missing.', () async { testUsingContext('throws, if Podfile is missing.', () async {
try { try {
await cocoaPodsUnderTest.processPods( await cocoaPodsUnderTest.processPods(
iosProject: projectUnderTest.ios, xcodeProject: projectUnderTest.ios,
iosEngineDir: 'engine/path', engineDir: 'engine/path',
); );
fail('ToolExit expected'); fail('ToolExit expected');
} catch(e) { } catch(e) {
...@@ -316,8 +335,8 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by ...@@ -316,8 +335,8 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by
)); ));
try { try {
await cocoaPodsUnderTest.processPods( await cocoaPodsUnderTest.processPods(
iosProject: projectUnderTest.ios, xcodeProject: projectUnderTest.ios,
iosEngineDir: 'engine/path', engineDir: 'engine/path',
); );
fail('ToolExit expected'); fail('ToolExit expected');
} catch (e) { } catch (e) {
...@@ -340,8 +359,8 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by ...@@ -340,8 +359,8 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by
..createSync(recursive: true) ..createSync(recursive: true)
..writeAsStringSync('Existing lock file.'); ..writeAsStringSync('Existing lock file.');
final bool didInstall = await cocoaPodsUnderTest.processPods( final bool didInstall = await cocoaPodsUnderTest.processPods(
iosProject: projectUnderTest.ios, xcodeProject: projectUnderTest.ios,
iosEngineDir: 'engine/path', engineDir: 'engine/path',
dependenciesChanged: false, dependenciesChanged: false,
); );
expect(didInstall, isTrue); expect(didInstall, isTrue);
...@@ -363,8 +382,8 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by ...@@ -363,8 +382,8 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by
..createSync() ..createSync()
..writeAsStringSync('Existing lock file.'); ..writeAsStringSync('Existing lock file.');
final bool didInstall = await cocoaPodsUnderTest.processPods( final bool didInstall = await cocoaPodsUnderTest.processPods(
iosProject: projectUnderTest.ios, xcodeProject: projectUnderTest.ios,
iosEngineDir: 'engine/path', engineDir: 'engine/path',
dependenciesChanged: false, dependenciesChanged: false,
); );
expect(didInstall, isTrue); expect(didInstall, isTrue);
...@@ -392,8 +411,8 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by ...@@ -392,8 +411,8 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by
..createSync(recursive: true) ..createSync(recursive: true)
..writeAsStringSync('Different lock file.'); ..writeAsStringSync('Different lock file.');
final bool didInstall = await cocoaPodsUnderTest.processPods( final bool didInstall = await cocoaPodsUnderTest.processPods(
iosProject: projectUnderTest.ios, xcodeProject: projectUnderTest.ios,
iosEngineDir: 'engine/path', engineDir: 'engine/path',
dependenciesChanged: false, dependenciesChanged: false,
); );
expect(didInstall, isTrue); expect(didInstall, isTrue);
...@@ -421,8 +440,8 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by ...@@ -421,8 +440,8 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by
..createSync(recursive: true) ..createSync(recursive: true)
..writeAsStringSync('Existing lock file.'); ..writeAsStringSync('Existing lock file.');
final bool didInstall = await cocoaPodsUnderTest.processPods( final bool didInstall = await cocoaPodsUnderTest.processPods(
iosProject: projectUnderTest.ios, xcodeProject: projectUnderTest.ios,
iosEngineDir: 'engine/path', engineDir: 'engine/path',
dependenciesChanged: true, dependenciesChanged: true,
); );
expect(didInstall, isTrue); expect(didInstall, isTrue);
...@@ -453,8 +472,8 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by ...@@ -453,8 +472,8 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by
projectUnderTest.ios.podfile projectUnderTest.ios.podfile
..writeAsStringSync('Updated Podfile'); ..writeAsStringSync('Updated Podfile');
await cocoaPodsUnderTest.processPods( await cocoaPodsUnderTest.processPods(
iosProject: projectUnderTest.ios, xcodeProject: projectUnderTest.ios,
iosEngineDir: 'engine/path', engineDir: 'engine/path',
dependenciesChanged: false, dependenciesChanged: false,
); );
verify(mockProcessManager.run( verify(mockProcessManager.run(
...@@ -481,8 +500,8 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by ...@@ -481,8 +500,8 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by
..createSync(recursive: true) ..createSync(recursive: true)
..writeAsStringSync('Existing lock file.'); ..writeAsStringSync('Existing lock file.');
final bool didInstall = await cocoaPodsUnderTest.processPods( final bool didInstall = await cocoaPodsUnderTest.processPods(
iosProject: projectUnderTest.ios, xcodeProject: projectUnderTest.ios,
iosEngineDir: 'engine/path', engineDir: 'engine/path',
dependenciesChanged: false, dependenciesChanged: false,
); );
expect(didInstall, isFalse); expect(didInstall, isFalse);
...@@ -520,8 +539,8 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by ...@@ -520,8 +539,8 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by
try { try {
await cocoaPodsUnderTest.processPods( await cocoaPodsUnderTest.processPods(
iosProject: projectUnderTest.ios, xcodeProject: projectUnderTest.ios,
iosEngineDir: 'engine/path', engineDir: 'engine/path',
dependenciesChanged: true, dependenciesChanged: true,
); );
fail('Tool throw expected when pod install fails'); fail('Tool throw expected when pod install fails');
...@@ -557,8 +576,8 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by ...@@ -557,8 +576,8 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by
environment: environment, environment: environment,
)).thenAnswer((_) async => exitsHappy()); )).thenAnswer((_) async => exitsHappy());
final bool success = await cocoaPodsUnderTest.processPods( final bool success = await cocoaPodsUnderTest.processPods(
iosProject: projectUnderTest.ios, xcodeProject: projectUnderTest.ios,
iosEngineDir: 'engine/path', engineDir: 'engine/path',
); );
expect(success, true); expect(success, true);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment