Support materializing Flutter module host app on iOS (#21276) * Prototype * Fix paths to Flutter library resources * Invoke pod install as necessary for materialized modules * Add devicelab test for module use on iOS * Remove debug output * Rebase, reame materialize editable * Add devicelab test editable iOS host app * Removed add2app test section
diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart index ed8258c..9df371a 100644 --- a/packages/flutter_tools/lib/src/project.dart +++ b/packages/flutter_tools/lib/src/project.dart
@@ -157,37 +157,53 @@ /// The parent of this project. final FlutterProject parent; - /// The directory of this project. - Directory get directory => parent.directory.childDirectory(isModule ? '.ios' : 'ios'); + Directory get _ephemeralDirectory => parent.directory.childDirectory('.ios'); + Directory get _editableDirectory => parent.directory.childDirectory('ios'); + /// This parent folder of `Runner.xcodeproj`. + Directory get hostAppRoot { + if (!isModule || _editableDirectory.existsSync()) + return _editableDirectory; + return _ephemeralDirectory; + } + + /// The root directory of the iOS wrapping of Flutter and plugins. This is the + /// parent of the `Flutter/` folder into which Flutter artifacts are written + /// during build. + /// + /// This is the same as [hostAppRoot] except when the project is + /// a Flutter module with an editable host app. + Directory get _flutterLibRoot => isModule ? _ephemeralDirectory : _editableDirectory; + + /// The bundle name of the host app, `Runner.app`. String get hostAppBundleName => '$_hostAppBundleName.app'; /// True, if the parent Flutter project is a module. bool get isModule => parent.isModule; /// The xcode config file for [mode]. - File xcodeConfigFor(String mode) => directory.childDirectory('Flutter').childFile('$mode.xcconfig'); + File xcodeConfigFor(String mode) => _flutterLibRoot.childDirectory('Flutter').childFile('$mode.xcconfig'); /// The 'Podfile'. - File get podfile => directory.childFile('Podfile'); + File get podfile => hostAppRoot.childFile('Podfile'); /// The 'Podfile.lock'. - File get podfileLock => directory.childFile('Podfile.lock'); + File get podfileLock => hostAppRoot.childFile('Podfile.lock'); /// The 'Manifest.lock'. - File get podManifestLock => directory.childDirectory('Pods').childFile('Manifest.lock'); + File get podManifestLock => hostAppRoot.childDirectory('Pods').childFile('Manifest.lock'); /// The 'Info.plist' file of the host app. - File get hostInfoPlist => directory.childDirectory(_hostAppBundleName).childFile('Info.plist'); + File get hostInfoPlist => hostAppRoot.childDirectory(_hostAppBundleName).childFile('Info.plist'); /// '.xcodeproj' folder of the host app. - Directory get xcodeProject => directory.childDirectory('$_hostAppBundleName.xcodeproj'); + Directory get xcodeProject => hostAppRoot.childDirectory('$_hostAppBundleName.xcodeproj'); /// The '.pbxproj' file of the host app. File get xcodeProjectInfoFile => xcodeProject.childFile('project.pbxproj'); /// Xcode workspace directory of the host app. - Directory get xcodeWorkspace => directory.childDirectory('$_hostAppBundleName.xcworkspace'); + Directory get xcodeWorkspace => hostAppRoot.childDirectory('$_hostAppBundleName.xcworkspace'); /// Xcode workspace shared data directory for the host app. Directory get xcodeWorkspaceSharedData => xcodeWorkspace.childDirectory('xcshareddata'); @@ -232,8 +248,12 @@ Future<void> ensureReadyForPlatformSpecificTooling() async { _regenerateFromTemplateIfNeeded(); - if (!directory.existsSync()) + if (!_flutterLibRoot.existsSync()) return; + await _updateGeneratedXcodeConfigIfNeeded(); + } + + Future<void> _updateGeneratedXcodeConfigIfNeeded() async { if (Cache.instance.isOlderThanToolsStamp(generatedXcodePropertiesFile)) { await xcode.updateGeneratedXcodeProperties( project: parent, @@ -246,28 +266,40 @@ void _regenerateFromTemplateIfNeeded() { if (!isModule) return; - final bool pubspecChanged = isOlderThanReference(entity: directory, referenceFile: parent.pubspecFile); - final bool toolingChanged = Cache.instance.isOlderThanToolsStamp(directory); + final bool pubspecChanged = isOlderThanReference(entity: _ephemeralDirectory, referenceFile: parent.pubspecFile); + final bool toolingChanged = Cache.instance.isOlderThanToolsStamp(_ephemeralDirectory); if (!pubspecChanged && !toolingChanged) return; - _deleteIfExistsSync(directory); - _overwriteFromTemplate(fs.path.join('module', 'ios', 'library'), directory); - _overwriteFromTemplate(fs.path.join('module', 'ios', 'host_app_ephemeral'), directory); - if (hasPlugins(parent)) { - _overwriteFromTemplate(fs.path.join('module', 'ios', 'host_app_ephemeral_cocoapods'), directory); + _deleteIfExistsSync(_ephemeralDirectory); + _overwriteFromTemplate(fs.path.join('module', 'ios', 'library'), _ephemeralDirectory); + // Add ephemeral host app, if a editable host app does not already exist. + if (!_editableDirectory.existsSync()) { + _overwriteFromTemplate(fs.path.join('module', 'ios', 'host_app_ephemeral'), _ephemeralDirectory); + if (hasPlugins(parent)) { + _overwriteFromTemplate(fs.path.join('module', 'ios', 'host_app_ephemeral_cocoapods'), _ephemeralDirectory); + } } } Future<void> makeHostAppEditable() async { - throwToolExit('making host app editable has not yet been implemented for iOS'); + assert(isModule); + if (_editableDirectory.existsSync()) + throwToolExit('iOS host app is already editable. To start fresh, delete the ios/ folder.'); + _deleteIfExistsSync(_ephemeralDirectory); + _overwriteFromTemplate(fs.path.join('module', 'ios', 'library'), _ephemeralDirectory); + _overwriteFromTemplate(fs.path.join('module', 'ios', 'host_app_ephemeral'), _editableDirectory); + _overwriteFromTemplate(fs.path.join('module', 'ios', 'host_app_ephemeral_cocoapods'), _editableDirectory); + _overwriteFromTemplate(fs.path.join('module', 'ios', 'host_app_editable_cocoapods'), _editableDirectory); + await _updateGeneratedXcodeConfigIfNeeded(); + await injectPlugins(parent); } - File get generatedXcodePropertiesFile => directory.childDirectory('Flutter').childFile('Generated.xcconfig'); + File get generatedXcodePropertiesFile => _flutterLibRoot.childDirectory('Flutter').childFile('Generated.xcconfig'); Directory get pluginRegistrantHost { return isModule - ? directory.childDirectory('Flutter').childDirectory('FlutterPluginRegistrant') - : directory.childDirectory(_hostAppBundleName); + ? _flutterLibRoot.childDirectory('Flutter').childDirectory('FlutterPluginRegistrant') + : hostAppRoot.childDirectory(_hostAppBundleName); } void _overwriteFromTemplate(String path, Directory target) {