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.
diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart
index 1b7a2e2..59a0153 100644
--- a/packages/flutter_tools/lib/src/ios/mac.dart
+++ b/packages/flutter_tools/lib/src/ios/mac.dart
@@ -10,7 +10,6 @@
import '../base/common.dart';
import '../base/context.dart';
import '../base/file_system.dart';
-import '../base/fingerprint.dart';
import '../base/io.dart';
import '../base/logger.dart';
import '../base/os.dart';
@@ -21,9 +20,8 @@
import '../build_info.dart';
import '../convert.dart';
import '../globals.dart';
-import '../macos/cocoapods.dart';
+import '../macos/cocoapod_utils.dart';
import '../macos/xcode.dart';
-import '../plugins.dart';
import '../project.dart';
import '../services.dart';
import 'code_signing.dart';
@@ -274,29 +272,7 @@
targetOverride: targetOverride,
buildInfo: buildInfo,
);
- refreshPluginsList(project);
- 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();
- }
+ await processPodsIfNeeded(project.ios, getIosBuildDirectory(), buildInfo.mode);
final List<String> buildCommands = <String>[
'/usr/bin/env',
diff --git a/packages/flutter_tools/lib/src/macos/build_macos.dart b/packages/flutter_tools/lib/src/macos/build_macos.dart
index 07c2c8c..12d2787 100644
--- a/packages/flutter_tools/lib/src/macos/build_macos.dart
+++ b/packages/flutter_tools/lib/src/macos/build_macos.dart
@@ -12,6 +12,7 @@
import '../globals.dart';
import '../ios/xcodeproj.dart';
import '../project.dart';
+import 'cocoapod_utils.dart';
/// Builds the macOS project through xcode build.
// TODO(jonahwilliams): support target option.
@@ -28,6 +29,8 @@
useMacOSConfig: true,
setSymroot: false,
);
+ await processPodsIfNeeded(flutterProject.macos, getMacOSBuildDirectory(), buildInfo.mode);
+
// Set debug or release mode.
String config = 'Debug';
if (buildInfo.isRelease) {
diff --git a/packages/flutter_tools/lib/src/macos/cocoapod_utils.dart b/packages/flutter_tools/lib/src/macos/cocoapod_utils.dart
new file mode 100644
index 0000000..aa7f701
--- /dev/null
+++ b/packages/flutter_tools/lib/src/macos/cocoapod_utils.dart
@@ -0,0 +1,46 @@
+// 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();
+ }
+}
diff --git a/packages/flutter_tools/lib/src/macos/cocoapods.dart b/packages/flutter_tools/lib/src/macos/cocoapods.dart
index c85555c..868b0e9 100644
--- a/packages/flutter_tools/lib/src/macos/cocoapods.dart
+++ b/packages/flutter_tools/lib/src/macos/cocoapods.dart
@@ -100,18 +100,18 @@
}
Future<bool> processPods({
- @required IosProject iosProject,
+ @required XcodeBasedProject xcodeProject,
// For backward compatibility with previously created Podfile only.
- @required String iosEngineDir,
+ @required String engineDir,
bool isSwift = false,
bool dependenciesChanged = true,
}) async {
- if (!(await iosProject.podfile.exists())) {
+ if (!(await xcodeProject.podfile.exists())) {
throwToolExit('Podfile missing');
}
if (await _checkPodCondition()) {
- if (_shouldRunPodInstall(iosProject, dependenciesChanged)) {
- await _runPodInstall(iosProject, iosEngineDir);
+ if (_shouldRunPodInstall(xcodeProject, dependenciesChanged)) {
+ await _runPodInstall(xcodeProject, engineDir);
return true;
}
}
@@ -176,46 +176,52 @@
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
/// include pods configuration.
- void setupPodfile(IosProject iosProject) {
+ void setupPodfile(XcodeBasedProject xcodeProject) {
if (!xcodeProjectInterpreter.isInstalled) {
// Don't do anything for iOS when host platform doesn't support it.
return;
}
- final Directory runnerProject = iosProject.xcodeProject;
+ final Directory runnerProject = xcodeProject.xcodeProject;
if (!runnerProject.existsSync()) {
return;
}
- final File podfile = iosProject.podfile;
+ final File podfile = xcodeProject.podfile;
if (!podfile.existsSync()) {
- final bool isSwift = xcodeProjectInterpreter.getBuildSettings(
- runnerProject.path,
- 'Runner',
- ).containsKey('SWIFT_VERSION');
+ String podfileTemplateName;
+ if (xcodeProject is MacOSProject) {
+ podfileTemplateName = 'Podfile-macos';
+ } 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(
Cache.flutterRoot,
'packages',
'flutter_tools',
'templates',
'cocoapods',
- isSwift ? 'Podfile-swift' : 'Podfile-objc',
+ podfileTemplateName,
));
podfileTemplate.copySync(podfile.path);
}
- addPodsDependencyToFlutterXcconfig(iosProject);
+ addPodsDependencyToFlutterXcconfig(xcodeProject);
}
- /// Ensures all `Flutter/Xxx.xcconfig` files for the given iOS sub-project of
- /// a parent Flutter project include pods configuration.
- void addPodsDependencyToFlutterXcconfig(IosProject iosProject) {
- _addPodsDependencyToFlutterXcconfig(iosProject, 'Debug');
- _addPodsDependencyToFlutterXcconfig(iosProject, 'Release');
+ /// Ensures all `Flutter/Xxx.xcconfig` files for the given Xcode-based
+ /// sub-project of a parent Flutter project include pods configuration.
+ void addPodsDependencyToFlutterXcconfig(XcodeBasedProject xcodeProject) {
+ _addPodsDependencyToFlutterXcconfig(xcodeProject, 'Debug');
+ _addPodsDependencyToFlutterXcconfig(xcodeProject, 'Release');
}
- void _addPodsDependencyToFlutterXcconfig(IosProject iosProject, String mode) {
- final File file = iosProject.xcodeConfigFor(mode);
+ void _addPodsDependencyToFlutterXcconfig(XcodeBasedProject xcodeProject, String mode) {
+ final File file = xcodeProject.xcodeConfigFor(mode);
if (file.existsSync()) {
final String content = file.readAsStringSync();
final String include = '#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.${mode
@@ -226,8 +232,8 @@
}
/// Ensures that pod install is deemed needed on next check.
- void invalidatePodInstallOutput(IosProject iosProject) {
- final File manifestLock = iosProject.podManifestLock;
+ void invalidatePodInstallOutput(XcodeBasedProject xcodeProject) {
+ final File manifestLock = xcodeProject.podManifestLock;
if (manifestLock.existsSync()) {
manifestLock.deleteSync();
}
@@ -239,13 +245,13 @@
// 2. Podfile.lock doesn't exist or is older than Podfile
// 3. Pods/Manifest.lock doesn't exist (It is deleted when plugins change)
// 4. Podfile.lock doesn't match Pods/Manifest.lock.
- bool _shouldRunPodInstall(IosProject iosProject, bool dependenciesChanged) {
+ bool _shouldRunPodInstall(XcodeBasedProject xcodeProject, bool dependenciesChanged) {
if (dependenciesChanged)
return true;
- final File podfileFile = iosProject.podfile;
- final File podfileLockFile = iosProject.podfileLock;
- final File manifestLockFile = iosProject.podManifestLock;
+ final File podfileFile = xcodeProject.podfile;
+ final File podfileLockFile = xcodeProject.podfileLock;
+ final File manifestLockFile = xcodeProject.podManifestLock;
return !podfileLockFile.existsSync()
|| !manifestLockFile.existsSync()
@@ -253,11 +259,11 @@
|| 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 ProcessResult result = await processManager.run(
<String>['pod', 'install', '--verbose'],
- workingDirectory: iosProject.hostAppRoot.path,
+ workingDirectory: fs.path.dirname(xcodeProject.podfile.path),
environment: <String, String>{
// For backward compatibility with previously created Podfile only.
'FLUTTER_FRAMEWORK_DIR': engineDirectory,
@@ -278,7 +284,7 @@
}
}
if (result.exitCode != 0) {
- invalidatePodInstallOutput(iosProject);
+ invalidatePodInstallOutput(xcodeProject);
_diagnosePodInstallFailure(result);
throwToolExit('Error running pod install');
}
diff --git a/packages/flutter_tools/lib/src/plugins.dart b/packages/flutter_tools/lib/src/plugins.dart
index e8c0905..5883d36 100644
--- a/packages/flutter_tools/lib/src/plugins.dart
+++ b/packages/flutter_tools/lib/src/plugins.dart
@@ -9,6 +9,7 @@
import 'base/file_system.dart';
import 'dart/package_map.dart';
+import 'desktop.dart';
import 'globals.dart';
import 'macos/cocoapods.dart';
import 'project.dart';
@@ -39,7 +40,9 @@
if (pluginYaml != null) {
androidPackage = pluginYaml['androidPackage'];
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'];
}
return Plugin(
@@ -179,14 +182,14 @@
_renderTemplateToFile(_androidPluginRegistryTemplate, context, registryPath);
}
-const String _iosPluginRegistryHeaderTemplate = '''//
+const String _cocoaPluginRegistryHeaderTemplate = '''//
// Generated file. Do not edit.
//
#ifndef GeneratedPluginRegistrant_h
#define GeneratedPluginRegistrant_h
-#import <Flutter/Flutter.h>
+#import <{{framework}}/{{framework}}.h>
@interface GeneratedPluginRegistrant : NSObject
+ (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry;
@@ -195,7 +198,7 @@
#endif /* GeneratedPluginRegistrant_h */
''';
-const String _iosPluginRegistryImplementationTemplate = '''//
+const String _cocoaPluginRegistryImplementationTemplate = '''//
// Generated file. Do not edit.
//
@@ -215,7 +218,7 @@
@end
''';
-const String _iosPluginRegistrantPodspecTemplate = '''
+const String _pluginRegistrantPodspecTemplate = '''
#
# Generated file, do not edit.
#
@@ -230,11 +233,11 @@
s.homepage = 'https://flutter.dev'
s.license = { :type => 'BSD' }
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 = { :path => '.' }
s.public_header_files = './Classes/**/*.h'
- s.dependency 'Flutter'
+ s.dependency '{{framework}}'
{{#plugins}}
s.dependency '{{name}}'
{{/plugins}}
@@ -250,36 +253,64 @@
'class': p.pluginClass,
}).toList();
final Map<String, dynamic> context = <String, dynamic>{
+ 'os': 'ios',
+ 'deploymentTarget': '8.0',
+ 'framework': 'Flutter',
'plugins': iosPlugins,
};
-
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) {
final String registryClassesDirectory = fs.path.join(registryDirectory, 'Classes');
_renderTemplateToFile(
- _iosPluginRegistrantPodspecTemplate,
- context,
+ _pluginRegistrantPodspecTemplate,
+ templateContext,
fs.path.join(registryDirectory, 'FlutterPluginRegistrant.podspec'),
);
_renderTemplateToFile(
- _iosPluginRegistryHeaderTemplate,
- context,
+ _cocoaPluginRegistryHeaderTemplate,
+ templateContext,
fs.path.join(registryClassesDirectory, 'GeneratedPluginRegistrant.h'),
);
_renderTemplateToFile(
- _iosPluginRegistryImplementationTemplate,
- context,
+ _cocoaPluginRegistryImplementationTemplate,
+ templateContext,
fs.path.join(registryClassesDirectory, 'GeneratedPluginRegistrant.m'),
);
} else {
_renderTemplateToFile(
- _iosPluginRegistryHeaderTemplate,
- context,
+ _cocoaPluginRegistryHeaderTemplate,
+ templateContext,
fs.path.join(registryDirectory, 'GeneratedPluginRegistrant.h'),
);
_renderTemplateToFile(
- _iosPluginRegistryImplementationTemplate,
- context,
+ _cocoaPluginRegistryImplementationTemplate,
+ templateContext,
fs.path.join(registryDirectory, 'GeneratedPluginRegistrant.m'),
);
}
@@ -317,17 +348,25 @@
if ((checkProjects && project.ios.existsSync()) || !checkProjects) {
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();
if (plugins.isNotEmpty) {
- cocoaPods.setupPodfile(project.ios);
+ cocoaPods.setupPodfile(subproject);
}
/// The user may have a custom maintained Podfile that they're running `pod install`
/// on themselves.
- else if (project.ios.podfile.existsSync() && project.ios.podfileLock.existsSync()) {
- cocoaPods.addPodsDependencyToFlutterXcconfig(project.ios);
+ else if (subproject.podfile.existsSync() && subproject.podfileLock.existsSync()) {
+ cocoaPods.addPodsDependencyToFlutterXcconfig(subproject);
}
}
+ }
}
/// Returns whether the specified Flutter [project] has any plugin dependencies.
diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart
index 970d9ee..26353ef 100644
--- a/packages/flutter_tools/lib/src/project.dart
+++ b/packages/flutter_tools/lib/src/project.dart
@@ -13,6 +13,7 @@
import 'build_info.dart';
import 'bundle.dart' as bundle;
import 'cache.dart';
+import 'desktop.dart';
import 'flutter_manifest.dart';
import 'ios/ios_workflow.dart';
import 'ios/plist_utils.dart' as plist;
@@ -179,6 +180,11 @@
if ((ios.existsSync() && checkProjects) || !checkProjects) {
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) {
await web.ensureReadyForPlatformSpecificTooling();
}
@@ -205,14 +211,53 @@
}
}
+/// 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.
///
/// Instances will reflect the contents of the `ios/` sub-folder of
/// Flutter applications and the `.ios/` sub-folder of Flutter module projects.
-class IosProject {
+class IosProject implements XcodeBasedProject {
IosProject.fromFlutter(this.parent);
- /// The parent of this project.
+ @override
final FlutterProject parent;
static final RegExp _productBundleIdPattern = RegExp(r'''^\s*PRODUCT_BUNDLE_IDENTIFIER\s*=\s*(["']?)(.*?)\1;\s*$''');
@@ -246,28 +291,28 @@
/// Whether the flutter application has an iOS project.
bool get exists => hostAppRoot.existsSync();
- /// The xcode config file for [mode].
+ @override
File xcodeConfigFor(String mode) => _flutterLibRoot.childDirectory('Flutter').childFile('$mode.xcconfig');
- /// The 'Podfile'.
+ @override
File get podfile => hostAppRoot.childFile('Podfile');
- /// The 'Podfile.lock'.
+ @override
File get podfileLock => hostAppRoot.childFile('Podfile.lock');
- /// The 'Manifest.lock'.
+ @override
File get podManifestLock => hostAppRoot.childDirectory('Pods').childFile('Manifest.lock');
/// The 'Info.plist' file of the host app.
File get hostInfoPlist => hostAppRoot.childDirectory(_hostAppBundleName).childFile('Info.plist');
- /// '.xcodeproj' folder of the host app.
+ @override
Directory get xcodeProject => hostAppRoot.childDirectory('$_hostAppBundleName.xcodeproj');
- /// The '.pbxproj' file of the host app.
+ @override
File get xcodeProjectInfoFile => xcodeProject.childFile('project.pbxproj');
- /// Xcode workspace directory of the host app.
+ @override
Directory get xcodeWorkspace => hostAppRoot.childDirectory('$_hostAppBundleName.xcworkspace');
/// Xcode workspace shared data directory for the host app.
@@ -276,7 +321,7 @@
/// Xcode workspace shared workspace settings file for the host app.
File get xcodeWorkspaceSharedSettings => xcodeWorkspaceSharedData.childFile('WorkspaceSettings.xcsettings');
- /// Whether the current flutter project has an iOS subproject.
+ @override
bool existsSync() {
return parent.isModule || _editableDirectory.existsSync();
}
@@ -304,7 +349,7 @@
return null;
}
- /// True, if the host app project is using Swift.
+ @override
bool get isSwift => buildSettings?.containsKey('SWIFT_VERSION') ?? false;
/// The build settings for the host app of this project, as a detached map.
@@ -364,6 +409,7 @@
await injectPlugins(parent);
}
+ @override
File get generatedXcodePropertiesFile => _flutterLibRoot.childDirectory('Flutter').childFile('Generated.xcconfig');
Directory get pluginRegistrantHost {
@@ -573,16 +619,18 @@
}
/// The macOS sub project.
-class MacOSProject {
- MacOSProject._(this.project);
+class MacOSProject implements XcodeBasedProject {
+ MacOSProject._(this.parent);
- final FlutterProject project;
+ @override
+ final FlutterProject parent;
static const String _hostAppBundleName = 'Runner';
+ @override
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
/// possible, files that are edited by Flutter tooling after initial project
@@ -594,24 +642,54 @@
/// checked in should live here.
Directory get ephemeralDirectory => managedDirectory.childDirectory('ephemeral');
- /// Contains definitions for FLUTTER_ROOT, LOCAL_ENGINE, and more flags for
- /// the Xcode build.
+ @override
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');
- /// 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');
- /// The Xcode workspace file.
+ @override
+ File get xcodeProjectInfoFile => xcodeProject.childFile('project.pbxproj');
+
+ @override
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.
///
/// Ideally this will be replaced in the future with inspection of the Runner
/// scheme's target.
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