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/bin/xcode_backend.sh b/packages/flutter_tools/bin/xcode_backend.sh index 100b158..655040a 100755 --- a/packages/flutter_tools/bin/xcode_backend.sh +++ b/packages/flutter_tools/bin/xcode_backend.sh
@@ -80,6 +80,9 @@ AssertExists "${project_path}" local derived_dir="${SOURCE_ROOT}/Flutter" + if [[ -e "${project_path}/.ios" ]]; then + derived_dir="${SOURCE_ROOT}/../.ios/Flutter" + fi RunCommand mkdir -p -- "$derived_dir" AssertExists "$derived_dir"
diff --git a/packages/flutter_tools/lib/src/ios/cocoapods.dart b/packages/flutter_tools/lib/src/ios/cocoapods.dart index 96f7d32..184a24c 100644 --- a/packages/flutter_tools/lib/src/ios/cocoapods.dart +++ b/packages/flutter_tools/lib/src/ios/cocoapods.dart
@@ -156,7 +156,7 @@ // Don't do anything for iOS when host platform doesn't support it. return; } - final Directory runnerProject = iosProject.directory.childDirectory('Runner.xcodeproj'); + final Directory runnerProject = iosProject.xcodeProject; if (!runnerProject.existsSync()) { return; } @@ -223,7 +223,7 @@ final Status status = logger.startProgress('Running pod install...', expectSlowOperation: true); final ProcessResult result = await processManager.run( <String>['pod', 'install', '--verbose'], - workingDirectory: iosProject.directory.path, + workingDirectory: iosProject.hostAppRoot.path, environment: <String, String>{ // For backward compatibility with previously created Podfile only. 'FLUTTER_FRAMEWORK_DIR': engineDirectory,
diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart index 53c822c..d86bf9b 100644 --- a/packages/flutter_tools/lib/src/ios/mac.dart +++ b/packages/flutter_tools/lib/src/ios/mac.dart
@@ -291,7 +291,7 @@ modern: false, ); - final XcodeProjectInfo projectInfo = xcodeProjectInterpreter.getInfo(app.project.directory.path); + final XcodeProjectInfo projectInfo = xcodeProjectInterpreter.getInfo(app.project.hostAppRoot.path); 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:'); @@ -326,7 +326,7 @@ // Before the build, all service definitions must be updated and the dylibs // copied over to a location that is suitable for Xcodebuild to find them. - await _addServicesToBundle(app.project.directory); + await _addServicesToBundle(app.project.hostAppRoot); final FlutterProject project = await FlutterProject.current(); await updateGeneratedXcodeProperties( @@ -334,8 +334,8 @@ targetOverride: targetOverride, buildInfo: buildInfo, ); - - if (hasPlugins(project)) { + 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( @@ -381,7 +381,7 @@ buildCommands.add('-allowProvisioningDeviceRegistration'); } - final List<FileSystemEntity> contents = app.project.directory.listSync(); + final List<FileSystemEntity> contents = app.project.hostAppRoot.listSync(); for (FileSystemEntity entity in contents) { if (fs.path.extension(entity.path) == '.xcworkspace') { buildCommands.addAll(<String>[ @@ -446,7 +446,7 @@ initialBuildStatus = logger.startProgress('Starting Xcode build...'); final RunResult buildResult = await runAsync( buildCommands, - workingDirectory: app.project.directory.path, + workingDirectory: app.project.hostAppRoot.path, allowReentrantFlutter: true ); buildSubStatus?.stop(); @@ -473,7 +473,7 @@ '-allowProvisioningDeviceRegistration', ].contains(buildCommand); }).toList(), - workingDirectory: app.project.directory.path, + workingDirectory: app.project.hostAppRoot.path, )); if (buildResult.exitCode != 0) { @@ -492,7 +492,7 @@ stderr: buildResult.stderr, xcodeBuildExecution: XcodeBuildExecution( buildCommands: buildCommands, - appDirectory: app.project.directory.path, + appDirectory: app.project.hostAppRoot.path, buildForPhysicalDevice: buildForDevice, buildSettings: buildSettings, ), @@ -677,7 +677,7 @@ assert(await xcodeProjectFile.exists()); final List<String> lines = await xcodeProjectFile.readAsLines(); - if (lines.any((String line) => line.contains('path = Flutter/flutter_assets'))) + if (lines.any((String line) => line.contains('flutter_assets in Resources'))) return true; const String l1 = ' 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };';
diff --git a/packages/flutter_tools/lib/src/plugins.dart b/packages/flutter_tools/lib/src/plugins.dart index ea5f798..d0906a4 100644 --- a/packages/flutter_tools/lib/src/plugins.dart +++ b/packages/flutter_tools/lib/src/plugins.dart
@@ -228,6 +228,7 @@ s.source_files = "Classes", "Classes/**/*.{h,m}" s.source = { :path => '.' } s.public_header_files = './Classes/**/*.h' + s.dependency 'Flutter' {{#plugins}} s.dependency '{{name}}' {{/plugins}} @@ -296,7 +297,7 @@ final List<Plugin> plugins = findPlugins(project); await _writeAndroidPluginRegistrant(project, plugins); await _writeIOSPluginRegistrant(project, plugins); - if (!project.isModule && project.ios.directory.existsSync()) { + if (!project.isModule && project.ios.hostAppRoot.existsSync()) { final CocoaPods cocoaPods = CocoaPods(); if (plugins.isNotEmpty) cocoaPods.setupPodfile(project.ios);
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) {
diff --git a/packages/flutter_tools/templates/module/ios/host_app_editable_cocoapods/Config.tmpl/Flutter.xcconfig b/packages/flutter_tools/templates/module/ios/host_app_editable_cocoapods/Config.tmpl/Flutter.xcconfig new file mode 100644 index 0000000..f6b0378 --- /dev/null +++ b/packages/flutter_tools/templates/module/ios/host_app_editable_cocoapods/Config.tmpl/Flutter.xcconfig
@@ -0,0 +1,2 @@ +#include "../../.ios/Flutter/Generated.xcconfig" +ENABLE_BITCODE=NO
diff --git a/packages/flutter_tools/templates/module/ios/host_app_ephemeral/Runner.xcodeproj.tmpl/project.pbxproj.tmpl b/packages/flutter_tools/templates/module/ios/host_app_ephemeral/Runner.xcodeproj.tmpl/project.pbxproj.tmpl index 7b0eeb1..8d4b827 100644 --- a/packages/flutter_tools/templates/module/ios/host_app_ephemeral/Runner.xcodeproj.tmpl/project.pbxproj.tmpl +++ b/packages/flutter_tools/templates/module/ios/host_app_ephemeral/Runner.xcodeproj.tmpl/project.pbxproj.tmpl
@@ -36,15 +36,15 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 2D5378251FAA1A9400D5DBA9 /* flutter_assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = flutter_assets; path = Flutter/flutter_assets; sourceTree = SOURCE_ROOT; }; - 741F495E21355F27001E2961 /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/engine/Flutter.framework; sourceTree = "<group>"; }; - 741F496521356807001E2961 /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = "<group>"; }; + 2D5378251FAA1A9400D5DBA9 /* flutter_assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = flutter_assets; path = ../.ios/Flutter/flutter_assets; sourceTree = SOURCE_ROOT; }; + 741F495E21355F27001E2961 /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = ../.ios/Flutter/engine/Flutter.framework; sourceTree = "<group>"; }; + 741F496521356807001E2961 /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = ../.ios/Flutter/App.framework; sourceTree = "<group>"; }; 74974046213559DB008C567A /* Release.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; }; 74974047213559DB008C567A /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; }; 7497404A213559E7008C567A /* Flutter.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Flutter.xcconfig; sourceTree = "<group>"; }; 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; }; 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; }; - 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = ../.ios/Flutter/Generated.xcconfig; sourceTree = "<group>"; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
diff --git a/packages/flutter_tools/templates/module/ios/host_app_ephemeral_cocoapods/Podfile.copy.tmpl b/packages/flutter_tools/templates/module/ios/host_app_ephemeral_cocoapods/Podfile.copy.tmpl index f11c13a..3457cfd 100644 --- a/packages/flutter_tools/templates/module/ios/host_app_ephemeral_cocoapods/Podfile.copy.tmpl +++ b/packages/flutter_tools/templates/module/ios/host_app_ephemeral_cocoapods/Podfile.copy.tmpl
@@ -2,5 +2,5 @@ target 'Runner' do flutter_application_path = '../' - eval(File.read(File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb'))) + eval(File.read(File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')), binding) end
diff --git a/packages/flutter_tools/templates/module/ios/library/Flutter.tmpl/podhelper.rb b/packages/flutter_tools/templates/module/ios/library/Flutter.tmpl/podhelper.rb index eec077a..0ac9d7c 100644 --- a/packages/flutter_tools/templates/module/ios/library/Flutter.tmpl/podhelper.rb +++ b/packages/flutter_tools/templates/module/ios/library/Flutter.tmpl/podhelper.rb
@@ -20,11 +20,6 @@ return pods_array end -# Prepare symlinks folder. We use symlinks to avoid having Podfile.lock -# referring to absolute paths on developers' machines. -system('rm -rf .symlinks') -system('mkdir -p .symlinks/plugins') - def flutter_root(f) generated_xcode_build_settings = parse_KV_file(File.join(f, File.join('.ios', 'Flutter', 'Generated.xcconfig'))) if generated_xcode_build_settings.empty? @@ -38,32 +33,26 @@ } end -framework_dir = File.join(File.expand_path(File.dirname(__FILE__)), 'Flutter') +framework_dir = File.join(flutter_application_path, '.ios', 'Flutter') + engine_dir = File.join(framework_dir, 'engine') if !File.exist?(engine_dir) # Copy the debug engine to have something to link against if the xcode backend script has not run yet. debug_framework_dir = File.join(flutter_root(flutter_application_path), 'bin', 'cache', 'artifacts', 'engine', 'ios') - FileUtils.mkdir(engine_dir) + FileUtils.mkdir_p(engine_dir) FileUtils.cp_r(File.join(debug_framework_dir, 'Flutter.framework'), engine_dir) FileUtils.cp(File.join(debug_framework_dir, 'Flutter.podspec'), engine_dir) end -symlink = File.join('.symlinks', 'flutter') +pod 'Flutter', :path => engine_dir +pod 'FlutterPluginRegistrant', :path => File.join(framework_dir, 'FlutterPluginRegistrant') -File.symlink(framework_dir, symlink) -pod 'Flutter', :path => File.join(symlink, 'engine') - - +symlinks_dir = File.join(framework_dir, '.symlinks') +FileUtils.mkdir_p(symlinks_dir) plugin_pods = parse_KV_file(File.join(flutter_application_path, '.flutter-plugins')) - plugin_pods.map { |r| - symlink = File.join('.symlinks', 'plugins', r[:name]) - + symlink = File.join(symlinks_dir, r[:name]) + FileUtils.rm_f(symlink) File.symlink(r[:path], symlink) pod r[:name], :path => File.join(symlink, 'ios') } - -symlink = File.join('.symlinks', 'FlutterApp') -File.symlink(File.absolute_path(flutter_application_path), symlink) - -pod 'FlutterPluginRegistrant', :path => File.join(symlink, '.ios', 'Flutter','FlutterPluginRegistrant')
diff --git a/packages/flutter_tools/test/ios/cocoapods_test.dart b/packages/flutter_tools/test/ios/cocoapods_test.dart index 435bb96..a69b5ac 100644 --- a/packages/flutter_tools/test/ios/cocoapods_test.dart +++ b/packages/flutter_tools/test/ios/cocoapods_test.dart
@@ -46,7 +46,7 @@ mockProcessManager = MockProcessManager(); mockXcodeProjectInterpreter = MockXcodeProjectInterpreter(); projectUnderTest = await FlutterProject.fromDirectory(fs.directory('project')); - projectUnderTest.ios.directory.childDirectory('Runner.xcodeproj').createSync(recursive: true); + projectUnderTest.ios.xcodeProject.createSync(recursive: true); cocoaPodsUnderTest = CocoaPods(); pretendPodVersionIs('1.5.0'); fs.file(fs.path.join(
diff --git a/packages/flutter_tools/test/project_test.dart b/packages/flutter_tools/test/project_test.dart index b0efc88..63e8057 100644 --- a/packages/flutter_tools/test/project_test.dart +++ b/packages/flutter_tools/test/project_test.dart
@@ -148,20 +148,20 @@ testInMemory('does nothing in plugin or package root project', () async { final FlutterProject project = await aPluginProject(); await project.ensureReadyForPlatformSpecificTooling(); - expectNotExists(project.ios.directory.childDirectory('Runner').childFile('GeneratedPluginRegistrant.h')); + expectNotExists(project.ios.hostAppRoot.childDirectory('Runner').childFile('GeneratedPluginRegistrant.h')); expectNotExists(androidPluginRegistrant(project.android.hostAppGradleRoot.childDirectory('app'))); - expectNotExists(project.ios.directory.childDirectory('Flutter').childFile('Generated.xcconfig')); + expectNotExists(project.ios.hostAppRoot.childDirectory('Flutter').childFile('Generated.xcconfig')); expectNotExists(project.android.hostAppGradleRoot.childFile('local.properties')); }); testInMemory('injects plugins for iOS', () async { final FlutterProject project = await someProject(); await project.ensureReadyForPlatformSpecificTooling(); - expectExists(project.ios.directory.childDirectory('Runner').childFile('GeneratedPluginRegistrant.h')); + expectExists(project.ios.hostAppRoot.childDirectory('Runner').childFile('GeneratedPluginRegistrant.h')); }); testInMemory('generates Xcode configuration for iOS', () async { final FlutterProject project = await someProject(); await project.ensureReadyForPlatformSpecificTooling(); - expectExists(project.ios.directory.childDirectory('Flutter').childFile('Generated.xcconfig')); + expectExists(project.ios.hostAppRoot.childDirectory('Flutter').childFile('Generated.xcconfig')); }); testInMemory('injects plugins for Android', () async { final FlutterProject project = await someProject(); @@ -183,7 +183,7 @@ testInMemory('creates iOS pod in module', () async { final FlutterProject project = await aModuleProject(); await project.ensureReadyForPlatformSpecificTooling(); - final Directory flutter = project.ios.directory.childDirectory('Flutter'); + final Directory flutter = project.ios.hostAppRoot.childDirectory('Flutter'); expectExists(flutter.childFile('podhelper.rb')); expectExists(flutter.childFile('Generated.xcconfig')); final Directory pluginRegistrantClasses = flutter @@ -201,7 +201,7 @@ expect(project.android.isModule, isTrue); expect(project.ios.isModule, isTrue); expect(project.android.hostAppGradleRoot.basename, '.android'); - expect(project.ios.directory.basename, '.ios'); + expect(project.ios.hostAppRoot.basename, '.ios'); }); testInMemory('is known for non-module', () async { final FlutterProject project = await someProject(); @@ -209,7 +209,7 @@ expect(project.android.isModule, isFalse); expect(project.ios.isModule, isFalse); expect(project.android.hostAppGradleRoot.basename, 'android'); - expect(project.ios.directory.basename, 'ios'); + expect(project.ios.hostAppRoot.basename, 'ios'); }); });