Move plugin injection to just after pub get (#14743)
diff --git a/packages/flutter_tools/lib/src/ios/cocoapods.dart b/packages/flutter_tools/lib/src/ios/cocoapods.dart index da63cdc..1d558d4 100644 --- a/packages/flutter_tools/lib/src/ios/cocoapods.dart +++ b/packages/flutter_tools/lib/src/ios/cocoapods.dart
@@ -16,6 +16,7 @@ import '../base/version.dart'; import '../cache.dart'; import '../globals.dart'; +import 'xcodeproj.dart'; const String noCocoaPodsConsequence = ''' CocoaPods is used to retrieve the iOS platform side's plugin code that responds to your plugin usage on the Dart side. @@ -60,13 +61,13 @@ // For backward compatibility with previously created Podfile only. @required String iosEngineDir, bool isSwift: false, - bool pluginOrFlutterPodChanged: true, + bool flutterPodChanged: true, }) async { + if (!(await appIosDir.childFile('Podfile').exists())) { + throwToolExit('Podfile missing'); + } if (await _checkPodCondition()) { - if (!fs.file(fs.path.join(appIosDir.path, 'Podfile')).existsSync()) { - await _createPodfile(appIosDir, isSwift); - } // TODO(xster): Add more logic for handling merge conflicts. - if (_shouldRunPodInstall(appIosDir.path, pluginOrFlutterPodChanged)) + if (_shouldRunPodInstall(appIosDir.path, flutterPodChanged)) await _runPodInstall(appIosDir, iosEngineDir); } } @@ -99,39 +100,69 @@ return true; } - Future<Null> _createPodfile(Directory bundle, bool isSwift) async { - final File podfileTemplate = fs.file(fs.path.join( - Cache.flutterRoot, - 'packages', - 'flutter_tools', - 'templates', - 'cocoapods', - isSwift ? 'Podfile-swift' : 'Podfile-objc', - )); - podfileTemplate.copySync(fs.path.join(bundle.path, 'Podfile')); + /// Ensures the `ios` sub-project of the Flutter project at [directory] + /// contains a suitable `Podfile` and that its `Flutter/Xxx.xcconfig` files + /// include pods configuration. + void setupPodfile(String directory) { + if (!xcodeProjectInterpreter.canInterpretXcodeProjects) { + // Don't do anything for iOS when host platform doesn't support it. + return; + } + final String podfilePath = fs.path.join(directory, 'ios', 'Podfile'); + if (!fs.file(podfilePath).existsSync()) { + final bool isSwift = xcodeProjectInterpreter.getBuildSettings( + fs.path.join(directory, 'ios', 'Runner.xcodeproj'), + 'Runner', + ).containsKey('SWIFT_VERSION'); + final File podfileTemplate = fs.file(fs.path.join( + Cache.flutterRoot, + 'packages', + 'flutter_tools', + 'templates', + 'cocoapods', + isSwift ? 'Podfile-swift' : 'Podfile-objc', + )); + podfileTemplate.copySync(podfilePath); + } + _addPodsDependencyToFlutterXcconfig(directory, 'Debug'); + _addPodsDependencyToFlutterXcconfig(directory, 'Release'); + } + + void _addPodsDependencyToFlutterXcconfig(String directory, String mode) { + final File file = fs.file(fs.path.join(directory, 'ios', 'Flutter', '$mode.xcconfig')); + if (file.existsSync()) { + final String content = file.readAsStringSync(); + final String include = '#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.${mode + .toLowerCase()}.xcconfig"'; + if (!content.contains(include)) + file.writeAsStringSync('$include\n$content', flush: true); + } + } + + /// Ensures that pod install is deemed needed on next check. + void invalidatePodInstallOutput(String directory) { + final File manifest = fs.file( + fs.path.join(directory, 'ios', 'Pods', 'Manifest.lock'), + ); + if (manifest.existsSync()) + manifest.deleteSync(); } // Check if you need to run pod install. // The pod install will run if any of below is true. - // 1. Any plugins changed (add/update/delete) - // 2. The flutter.framework has changed (debug/release/profile) - // 3. The podfile.lock doesn't exists - // 4. The Pods/manifest.lock doesn't exists - // 5. The podfile.lock doesn't match Pods/manifest.lock. - bool _shouldRunPodInstall(String appDir, bool pluginOrFlutterPodChanged) { - if (pluginOrFlutterPodChanged) + // 1. The flutter.framework has changed (debug/release/profile) + // 2. The podfile.lock doesn't exist + // 3. The Pods/Manifest.lock doesn't exist (It is deleted when plugins change) + // 4. The podfile.lock doesn't match Pods/Manifest.lock. + bool _shouldRunPodInstall(String appDir, bool flutterPodChanged) { + if (flutterPodChanged) return true; - // Check if podfile.lock and Pods/Manifest.lock exists and matches. + // Check if podfile.lock and Pods/Manifest.lock exist and match. final File podfileLockFile = fs.file(fs.path.join(appDir, 'Podfile.lock')); - final File manifestLockFile = - fs.file(fs.path.join(appDir, 'Pods', 'Manifest.lock')); - if (!podfileLockFile.existsSync() + final File manifestLockFile = fs.file(fs.path.join(appDir, 'Pods', 'Manifest.lock')); + return !podfileLockFile.existsSync() || !manifestLockFile.existsSync() - || podfileLockFile.readAsStringSync() != - manifestLockFile.readAsStringSync()) { - return true; - } - return false; + || podfileLockFile.readAsStringSync() != manifestLockFile.readAsStringSync(); } Future<Null> _runPodInstall(Directory bundle, String engineDirectory) async {
diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart index 32d37b1..b2a5f6f 100644 --- a/packages/flutter_tools/lib/src/ios/mac.dart +++ b/packages/flutter_tools/lib/src/ios/mac.dart
@@ -219,7 +219,7 @@ return new XcodeBuildResult(success: false); } - final XcodeProjectInfo projectInfo = new XcodeProjectInfo.fromProjectSync(app.appDirectory); + final XcodeProjectInfo projectInfo = xcodeProjectInterpreter.getInfo(app.appDirectory); if (!projectInfo.targets.contains('Runner')) { printError('The Xcode project does not define target "Runner" which is needed by Flutter tooling.'); printError('Open Xcode to fix the problem:'); @@ -256,26 +256,22 @@ // copied over to a location that is suitable for Xcodebuild to find them. final Directory appDirectory = fs.directory(app.appDirectory); await _addServicesToBundle(appDirectory); - final InjectPluginsResult injectPluginsResult = injectPlugins(); - final bool hasFlutterPlugins = injectPluginsResult.hasPlugin; final String previousGeneratedXcconfig = readGeneratedXcconfig(app.appDirectory); - updateXcodeGeneratedProperties( + updateGeneratedXcodeProperties( projectPath: fs.currentDirectory.path, buildInfo: buildInfo, target: target, - hasPlugins: hasFlutterPlugins, previewDart2: buildInfo.previewDart2, ); - if (hasFlutterPlugins) { + if (hasPlugins()) { final String currentGeneratedXcconfig = readGeneratedXcconfig(app.appDirectory); await cocoaPods.processPods( - appIosDir: appDirectory, - iosEngineDir: flutterFrameworkDir(buildInfo.mode), - isSwift: app.isSwift, - pluginOrFlutterPodChanged: injectPluginsResult.hasChanged - || previousGeneratedXcconfig != currentGeneratedXcconfig, + appIosDir: appDirectory, + iosEngineDir: flutterFrameworkDir(buildInfo.mode), + isSwift: app.isSwift, + flutterPodChanged: (previousGeneratedXcconfig != currentGeneratedXcconfig), ); } @@ -465,7 +461,7 @@ String readGeneratedXcconfig(String appPath) { final String generatedXcconfigPath = - fs.path.join(fs.currentDirectory.path, appPath, 'Flutter','Generated.xcconfig'); + fs.path.join(fs.currentDirectory.path, appPath, 'Flutter', 'Generated.xcconfig'); final File generatedXcconfigFile = fs.file(generatedXcconfigPath); if (!generatedXcconfigFile.existsSync()) return null;
diff --git a/packages/flutter_tools/lib/src/ios/xcodeproj.dart b/packages/flutter_tools/lib/src/ios/xcodeproj.dart index 1bc8e79..9a50d30 100644 --- a/packages/flutter_tools/lib/src/ios/xcodeproj.dart +++ b/packages/flutter_tools/lib/src/ios/xcodeproj.dart
@@ -5,11 +5,13 @@ import 'package:meta/meta.dart'; import '../artifacts.dart'; +import '../base/context.dart'; import '../base/file_system.dart'; import '../base/process.dart'; import '../base/utils.dart'; import '../build_info.dart'; import '../cache.dart'; +import '../flx.dart' as flx; import '../globals.dart'; final RegExp _settingExpr = new RegExp(r'(\w+)\s*=\s*(.*)$'); @@ -19,11 +21,28 @@ return fs.path.normalize(fs.path.dirname(artifacts.getArtifactPath(Artifact.flutterFramework, TargetPlatform.ios, mode))); } -void updateXcodeGeneratedProperties({ +String _generatedXcodePropertiesPath(String projectPath) { + return fs.path.join(projectPath, 'ios', 'Flutter', 'Generated.xcconfig'); +} + +/// Writes default Xcode properties files in the Flutter project at +/// [projectPath], if such files do not already exist. +void generateXcodeProperties(String projectPath) { + if (fs.file(_generatedXcodePropertiesPath(projectPath)).existsSync()) + return; + updateGeneratedXcodeProperties( + projectPath: projectPath, + buildInfo: BuildInfo.debug, + target: flx.defaultMainPath, + previewDart2: false, + ); +} + +/// Writes or rewrites Xcode property files with the specified information. +void updateGeneratedXcodeProperties({ @required String projectPath, @required BuildInfo buildInfo, @required String target, - @required bool hasPlugins, @required bool previewDart2, }) { final StringBuffer localsBuffer = new StringBuffer(); @@ -58,21 +77,42 @@ localsBuffer.writeln('PREVIEW_DART_2=true'); } - // Add dependency to CocoaPods' generated project only if plugins are used. - if (hasPlugins) - localsBuffer.writeln('#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"'); - - final File localsFile = fs.file(fs.path.join(projectPath, 'ios', 'Flutter', 'Generated.xcconfig')); + final File localsFile = fs.file(_generatedXcodePropertiesPath(projectPath)); localsFile.createSync(recursive: true); localsFile.writeAsStringSync(localsBuffer.toString()); } -Map<String, String> getXcodeBuildSettings(String xcodeProjPath, String target) { - final String absProjPath = fs.path.absolute(xcodeProjPath); - final String out = runCheckedSync(<String>[ - '/usr/bin/xcodebuild', '-project', absProjPath, '-target', target, '-showBuildSettings' - ]); - return parseXcodeBuildSettings(out); +XcodeProjectInterpreter get xcodeProjectInterpreter => context.putIfAbsent( + XcodeProjectInterpreter, + () => const XcodeProjectInterpreter(), +); + +/// Interpreter of Xcode projects settings. +class XcodeProjectInterpreter { + static const String _executable = '/usr/bin/xcodebuild'; + + const XcodeProjectInterpreter(); + + bool get canInterpretXcodeProjects => fs.isFileSync(_executable); + + Map<String, String> getBuildSettings(String projectPath, String target) { + final String out = runCheckedSync(<String>[ + _executable, + '-project', + fs.path.absolute(projectPath), + '-target', + target, + '-showBuildSettings' + ], workingDirectory: projectPath); + return parseXcodeBuildSettings(out); + } + + XcodeProjectInfo getInfo(String projectPath) { + final String out = runCheckedSync(<String>[ + _executable, '-list', + ], workingDirectory: projectPath); + return new XcodeProjectInfo.fromXcodeBuildOutput(out); + } } Map<String, String> parseXcodeBuildSettings(String showBuildSettingsOutput) { @@ -101,13 +141,6 @@ class XcodeProjectInfo { XcodeProjectInfo(this.targets, this.buildConfigurations, this.schemes); - factory XcodeProjectInfo.fromProjectSync(String projectPath) { - final String out = runCheckedSync(<String>[ - '/usr/bin/xcodebuild', '-list', - ], workingDirectory: projectPath); - return new XcodeProjectInfo.fromXcodeBuildOutput(out); - } - factory XcodeProjectInfo.fromXcodeBuildOutput(String output) { final List<String> targets = <String>[]; final List<String> buildConfigurations = <String>[];