Revert pod install skip revert (#13774) * Revert "Revert "Enable developers to run pod from terminal directly and skip pod install if possible. (#13374)" (#13770)" This reverts commit 0759043e47395b692bff7d6aa045c1ecb06627eb. * some nits on cocoapods code * put back the FLUTTER_FRAMEWORK_DIR env variable
diff --git a/packages/flutter_tools/lib/src/commands/inject_plugins.dart b/packages/flutter_tools/lib/src/commands/inject_plugins.dart index bf53cb5..f1df432 100644 --- a/packages/flutter_tools/lib/src/commands/inject_plugins.dart +++ b/packages/flutter_tools/lib/src/commands/inject_plugins.dart
@@ -24,7 +24,7 @@ @override Future<Null> runCommand() async { - final bool result = injectPlugins(); + final bool result = injectPlugins().hasPlugin; if (result) { printStatus('GeneratedPluginRegistrants successfully written.'); } else {
diff --git a/packages/flutter_tools/lib/src/ios/cocoapods.dart b/packages/flutter_tools/lib/src/ios/cocoapods.dart index 78fd425..9ebe95a 100644 --- a/packages/flutter_tools/lib/src/ios/cocoapods.dart +++ b/packages/flutter_tools/lib/src/ios/cocoapods.dart
@@ -57,18 +57,21 @@ Future<Null> processPods({ @required Directory appIosDir, + // For backward compatibility with previously created Podfile only. @required String iosEngineDir, bool isSwift: false, + bool pluginOrFlutterPodChanged: true, }) async { 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. - - await _runPodInstall(appIosDir, iosEngineDir); + if (_shouldRunPodInstall(appIosDir.path, pluginOrFlutterPodChanged)) + await _runPodInstall(appIosDir, iosEngineDir); } } + /// Make sure the CocoaPods tools are in the right states. Future<bool> _checkPodCondition() async { if (!await isCocoaPodsInstalledAndMeetsVersionCheck) { final String minimumVersion = cocoaPodsMinimumVersion; @@ -108,12 +111,36 @@ podfileTemplate.copySync(fs.path.join(bundle.path, 'Podfile')); } + // 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) + return true; + // Check if podfile.lock and Pods/Manifest.lock exists and matches. + 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() + || !manifestLockFile.existsSync() + || podfileLockFile.readAsStringSync() != + manifestLockFile.readAsStringSync()) { + return true; + } + return false; + } + Future<Null> _runPodInstall(Directory bundle, String engineDirectory) async { final Status status = logger.startProgress('Running pod install...', expectSlowOperation: true); final ProcessResult result = await processManager.run( <String>['pod', 'install', '--verbose'], workingDirectory: bundle.path, environment: <String, String>{ + // For backward compatibility with previously created Podfile only. 'FLUTTER_FRAMEWORK_DIR': engineDirectory, // See https://github.com/flutter/flutter/issues/10873. // CocoaPods analytics adds a lot of latency.
diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart index e3ead3b..2ca8109 100644 --- a/packages/flutter_tools/lib/src/ios/mac.dart +++ b/packages/flutter_tools/lib/src/ios/mac.dart
@@ -254,14 +254,9 @@ // copied over to a location that is suitable for Xcodebuild to find them. final Directory appDirectory = fs.directory(app.appDirectory); await _addServicesToBundle(appDirectory); - final bool hasFlutterPlugins = injectPlugins(); - - if (hasFlutterPlugins) - await cocoaPods.processPods( - appIosDir: appDirectory, - iosEngineDir: flutterFrameworkDir(buildInfo.mode), - isSwift: app.isSwift, - ); + final InjectPluginsResult injectPluginsResult = injectPlugins(); + final bool hasFlutterPlugins = injectPluginsResult.hasPlugin; + final String previousGeneratedXcconfig = readGeneratedXcconfig(app.appDirectory); updateXcodeGeneratedProperties( projectPath: fs.currentDirectory.path, @@ -271,6 +266,17 @@ previewDart2: buildInfo.previewDart2, ); + if (hasFlutterPlugins) { + final String currentGeneratedXcconfig = readGeneratedXcconfig(app.appDirectory); + await cocoaPods.processPods( + appIosDir: appDirectory, + iosEngineDir: flutterFrameworkDir(buildInfo.mode), + isSwift: app.isSwift, + pluginOrFlutterPodChanged: (injectPluginsResult.hasChanged + || previousGeneratedXcconfig != currentGeneratedXcconfig), + ); + } + final List<String> commands = <String>[ '/usr/bin/env', 'xcrun', @@ -355,7 +361,17 @@ } } -Future<Null> diagnoseXcodeBuildFailure(XcodeBuildResult result, BuildableIOSApp app) async { +String readGeneratedXcconfig(String appPath) { + final String generatedXcconfigPath = + fs.path.join(fs.currentDirectory.path, appPath, 'Flutter','Generated.xcconfig'); + final File generatedXcconfigFile = fs.file(generatedXcconfigPath); + if (!generatedXcconfigFile.existsSync()) + return null; + return generatedXcconfigFile.readAsStringSync(); +} + +Future<Null> diagnoseXcodeBuildFailure( + XcodeBuildResult result, BuildableIOSApp app) async { if (result.xcodeBuildExecution != null && result.xcodeBuildExecution.buildForPhysicalDevice && result.stdout?.contains('BCEROR') == true && @@ -369,7 +385,8 @@ // Make sure the user has specified one of: // DEVELOPMENT_TEAM (automatic signing) // PROVISIONING_PROFILE (manual signing) - !(app.buildSettings?.containsKey('DEVELOPMENT_TEAM')) == true || app.buildSettings?.containsKey('PROVISIONING_PROFILE') == true) { + !(app.buildSettings?.containsKey('DEVELOPMENT_TEAM')) == true + || app.buildSettings?.containsKey('PROVISIONING_PROFILE') == true) { printError(noDevelopmentTeamInstruction, emphasis: true); return; }
diff --git a/packages/flutter_tools/lib/src/plugins.dart b/packages/flutter_tools/lib/src/plugins.dart index 19d7bc0..60f7c5c 100644 --- a/packages/flutter_tools/lib/src/plugins.dart +++ b/packages/flutter_tools/lib/src/plugins.dart
@@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:meta/meta.dart'; import 'package:mustache/mustache.dart' as mustache; import 'package:yaml/yaml.dart'; @@ -77,10 +78,13 @@ return plugins; } -void _writeFlutterPluginsList(String directory, List<Plugin> plugins) { +/// Returns true if .flutter-plugins has changed, otherwise returns false. +bool _writeFlutterPluginsList(String directory, List<Plugin> plugins) { final File pluginsProperties = fs.file(fs.path.join(directory, '.flutter-plugins')); + final String previousFlutterPlugins = + (pluginsProperties.existsSync() ? pluginsProperties.readAsStringSync() : null); final String pluginManifest = - plugins.map((Plugin p) => '${p.name}=${escapePath(p.path)}').join('\n'); + plugins.map((Plugin p) => '${p.name}=${escapePath(p.path)}').join('\n'); if (pluginManifest.isNotEmpty) { pluginsProperties.writeAsStringSync('$pluginManifest\n'); } else { @@ -88,6 +92,9 @@ pluginsProperties.deleteSync(); } } + final String currentFlutterPlugins = + (pluginsProperties.existsSync() ? pluginsProperties.readAsStringSync() : null); + return currentFlutterPlugins != previousFlutterPlugins; } const String _androidPluginRegistryTemplate = '''package io.flutter.plugins; @@ -206,17 +213,28 @@ } +class InjectPluginsResult{ + InjectPluginsResult({ + @required this.hasPlugin, + @required this.hasChanged, + }); + /// True if any flutter plugin exists, otherwise false. + final bool hasPlugin; + /// True if plugins have changed since last build. + final bool hasChanged; +} + /// Finds Flutter plugins in the pubspec.yaml, creates platform injection /// registries classes and add them to the build dependencies. /// -/// Returns whether any Flutter plugins are added. -bool injectPlugins({String directory}) { +/// Returns whether any Flutter plugins are added and whether they changed. +InjectPluginsResult injectPlugins({String directory}) { directory ??= fs.currentDirectory.path; final List<Plugin> plugins = _findPlugins(directory); - _writeFlutterPluginsList(directory, plugins); + final bool hasPluginsChanged = _writeFlutterPluginsList(directory, plugins); if (fs.isDirectorySync(fs.path.join(directory, 'android'))) _writeAndroidPluginRegistry(directory, plugins); if (fs.isDirectorySync(fs.path.join(directory, 'ios'))) _writeIOSPluginRegistry(directory, plugins); - return plugins.isNotEmpty; + return new InjectPluginsResult(hasPlugin: plugins.isNotEmpty, hasChanged: hasPluginsChanged); }
diff --git a/packages/flutter_tools/templates/cocoapods/Podfile-objc b/packages/flutter_tools/templates/cocoapods/Podfile-objc index 90b5f65..3bc469f 100644 --- a/packages/flutter_tools/templates/cocoapods/Podfile-objc +++ b/packages/flutter_tools/templates/cocoapods/Podfile-objc
@@ -1,30 +1,44 @@ # Uncomment this line to define a global platform for your project # platform :ios, '9.0' -if ENV['FLUTTER_FRAMEWORK_DIR'] == nil - abort('Please set FLUTTER_FRAMEWORK_DIR to the directory containing Flutter.framework') -end - -target 'Runner' do - # Pods for Runner - - # Flutter Pods - pod 'Flutter', :path => ENV['FLUTTER_FRAMEWORK_DIR'] - - if File.exists? '../.flutter-plugins' - flutter_root = File.expand_path('..') - File.foreach('../.flutter-plugins') { |line| - plugin = line.split(pattern='=') +def parse_KV_file(file,seperator='=') + file_abs_path = File.expand_path(file) + if !File.exists? file_abs_path + return []; + end + pods_ary = [] + File.foreach(file_abs_path) { |line| + plugin = line.split(pattern=seperator) if plugin.length == 2 - name = plugin[0].strip() + podname = plugin[0].strip() path = plugin[1].strip() - resolved_path = File.expand_path("#{path}/ios", flutter_root) - pod name, :path => resolved_path + 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 + +target 'Runner' do + use_frameworks! + # Flutter Pods + generated_xcode_build_settings = parse_KV_file("./Flutter/Generated.xcconfig") + if generated_xcode_build_settings.empty? + puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter build or flutter run is executed once first." end + generated_xcode_build_settings.map{ |p| + if p[:name]=='FLUTTER_FRAMEWORK_DIR' + pod 'Flutter', :path => p[:path] + end + } + + # Plugin Pods + plugin_pods = parse_KV_file("../.flutter-plugins") + plugin_pods.map{ |p| + pod p[:name], :path => File.expand_path("ios",p[:path]) + } end post_install do |installer| @@ -33,4 +47,4 @@ config.build_settings['ENABLE_BITCODE'] = 'NO' end end -end +end \ No newline at end of file
diff --git a/packages/flutter_tools/templates/cocoapods/Podfile-swift b/packages/flutter_tools/templates/cocoapods/Podfile-swift index 74b3de0..3bc469f 100644 --- a/packages/flutter_tools/templates/cocoapods/Podfile-swift +++ b/packages/flutter_tools/templates/cocoapods/Podfile-swift
@@ -1,32 +1,44 @@ # Uncomment this line to define a global platform for your project # platform :ios, '9.0' -if ENV['FLUTTER_FRAMEWORK_DIR'] == nil - abort('Please set FLUTTER_FRAMEWORK_DIR to the directory containing Flutter.framework') +def parse_KV_file(file,seperator='=') + file_abs_path = File.expand_path(file) + if !File.exists? file_abs_path + return []; + end + pods_ary = [] + File.foreach(file_abs_path) { |line| + plugin = line.split(pattern=seperator) + 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 target 'Runner' do use_frameworks! - - # Pods for Runner - # Flutter Pods - pod 'Flutter', :path => ENV['FLUTTER_FRAMEWORK_DIR'] - - if File.exists? '../.flutter-plugins' - flutter_root = File.expand_path('..') - File.foreach('../.flutter-plugins') { |line| - plugin = line.split(pattern='=') - if plugin.length == 2 - name = plugin[0].strip() - path = plugin[1].strip() - resolved_path = File.expand_path("#{path}/ios", flutter_root) - pod name, :path => resolved_path - else - puts "Invalid plugin specification: #{line}" - end - } + generated_xcode_build_settings = parse_KV_file("./Flutter/Generated.xcconfig") + if generated_xcode_build_settings.empty? + puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter build or flutter run is executed once first." end + generated_xcode_build_settings.map{ |p| + if p[:name]=='FLUTTER_FRAMEWORK_DIR' + pod 'Flutter', :path => p[:path] + end + } + + # Plugin Pods + plugin_pods = parse_KV_file("../.flutter-plugins") + plugin_pods.map{ |p| + pod p[:name], :path => File.expand_path("ios",p[:path]) + } end post_install do |installer| @@ -35,4 +47,4 @@ config.build_settings['ENABLE_BITCODE'] = 'NO' end end -end +end \ No newline at end of file
diff --git a/packages/flutter_tools/test/ios/cocoapods_test.dart b/packages/flutter_tools/test/ios/cocoapods_test.dart index 493468f..e010d1a 100644 --- a/packages/flutter_tools/test/ios/cocoapods_test.dart +++ b/packages/flutter_tools/test/ios/cocoapods_test.dart
@@ -95,7 +95,8 @@ await cocoaPodsUnderTest.processPods( appIosDir: projectUnderTest, iosEngineDir: 'engine/path', - ); expect(fs.file(fs.path.join('project', 'ios', 'Podfile')).readAsStringSync() , 'Existing Podfile'); + ); + expect(fs.file(fs.path.join('project', 'ios', 'Podfile')).readAsStringSync() , 'Existing Podfile'); verify(mockProcessManager.run( <String>['pod', 'install', '--verbose'], workingDirectory: 'project/ios', @@ -177,6 +178,64 @@ ProcessManager: () => mockProcessManager, }, ); + + testUsingContext( + 'Run pod install if plugins or flutter framework have changes.', + () async { + fs.file(fs.path.join('project', 'ios', 'Podfile')) + ..createSync() + ..writeAsString('Existing Podfile'); + fs.file(fs.path.join('project', 'ios', 'Podfile.lock')) + ..createSync() + ..writeAsString('Existing lock files.'); + fs.file(fs.path.join('project', 'ios', 'Pods','Manifest.lock')) + ..createSync(recursive: true) + ..writeAsString('Existing lock files.'); + await cocoaPodsUnderTest.processPods( + appIosDir: projectUnderTest, + iosEngineDir: 'engine/path', + pluginOrFlutterPodChanged: true + ); + verify(mockProcessManager.run( + <String>['pod', 'install', '--verbose'], + workingDirectory: 'project/ios', + environment: <String, String>{'FLUTTER_FRAMEWORK_DIR': 'engine/path', 'COCOAPODS_DISABLE_STATS': 'true'}, + )); + }, + overrides: <Type, Generator>{ + FileSystem: () => fs, + ProcessManager: () => mockProcessManager, + }, + ); + + testUsingContext( + 'Skip pod install if plugins and flutter framework remain unchanged.', + () async { + fs.file(fs.path.join('project', 'ios', 'Podfile')) + ..createSync() + ..writeAsString('Existing Podfile'); + fs.file(fs.path.join('project', 'ios', 'Podfile.lock')) + ..createSync() + ..writeAsString('Existing lock files.'); + fs.file(fs.path.join('project', 'ios', 'Pods','Manifest.lock')) + ..createSync(recursive: true) + ..writeAsString('Existing lock files.'); + await cocoaPodsUnderTest.processPods( + appIosDir: projectUnderTest, + iosEngineDir: 'engine/path', + pluginOrFlutterPodChanged: false + ); + verifyNever(mockProcessManager.run( + <String>['pod', 'install', '--verbose'], + workingDirectory: 'project/ios', + environment: <String, String>{'FLUTTER_FRAMEWORK_DIR': 'engine/path', 'COCOAPODS_DISABLE_STATS': 'true'}, + )); + }, + overrides: <Type, Generator>{ + FileSystem: () => fs, + ProcessManager: () => mockProcessManager, + }, + ); } class MockProcessManager extends Mock implements ProcessManager {}