Unverified Commit cdbdafa8 authored by Chris Bracken's avatar Chris Bracken Committed by GitHub

Rerun pod install on changed Xcode project, Podfile (#17274)

If the developer changes their Xcode build settings and their project
has plugins, pod install is required, (e.g. to pick up changes to the
target architecture).

Similarly, manual edits to the Podfile should trigger a pod install.
parent b2c98f9a
...@@ -58,21 +58,23 @@ class CocoaPods { ...@@ -58,21 +58,23 @@ class CocoaPods {
/// Whether CocoaPods ran 'pod setup' once where the costly pods' specs are cloned. /// Whether CocoaPods ran 'pod setup' once where the costly pods' specs are cloned.
Future<bool> get isCocoaPodsInitialized => fs.isDirectory(fs.path.join(homeDirPath, '.cocoapods', 'repos', 'master')); Future<bool> get isCocoaPodsInitialized => fs.isDirectory(fs.path.join(homeDirPath, '.cocoapods', 'repos', 'master'));
Future<Null> processPods({ Future<bool> processPods({
@required Directory appIosDirectory, @required Directory appIosDirectory,
// For backward compatibility with previously created Podfile only. // For backward compatibility with previously created Podfile only.
@required String iosEngineDir, @required String iosEngineDir,
bool isSwift: false, bool isSwift: false,
bool flutterPodChanged: true, bool dependenciesChanged: true,
}) async { }) async {
if (!(await appIosDirectory.childFile('Podfile').exists())) { if (!(await appIosDirectory.childFile('Podfile').exists())) {
throwToolExit('Podfile missing'); throwToolExit('Podfile missing');
} }
if (await _checkPodCondition()) { if (await _checkPodCondition()) {
if (_shouldRunPodInstall(appIosDirectory, flutterPodChanged)) { if (_shouldRunPodInstall(appIosDirectory, dependenciesChanged)) {
await _runPodInstall(appIosDirectory, iosEngineDir); await _runPodInstall(appIosDirectory, iosEngineDir);
return true;
} }
} }
return false;
} }
/// Make sure the CocoaPods tools are in the right states. /// Make sure the CocoaPods tools are in the right states.
...@@ -157,8 +159,8 @@ class CocoaPods { ...@@ -157,8 +159,8 @@ class CocoaPods {
// 2. The podfile.lock doesn't exist // 2. The podfile.lock doesn't exist
// 3. The Pods/Manifest.lock doesn't exist (It is deleted when plugins change) // 3. The Pods/Manifest.lock doesn't exist (It is deleted when plugins change)
// 4. The podfile.lock doesn't match Pods/Manifest.lock. // 4. The podfile.lock doesn't match Pods/Manifest.lock.
bool _shouldRunPodInstall(Directory appIosDirectory, bool flutterPodChanged) { bool _shouldRunPodInstall(Directory appIosDirectory, bool dependenciesChanged) {
if (flutterPodChanged) if (dependenciesChanged)
return true; return true;
// Check if podfile.lock and Pods/Manifest.lock exist and match. // Check if podfile.lock and Pods/Manifest.lock exist and match.
final File podfileLockFile = appIosDirectory.childFile('Podfile.lock'); final File podfileLockFile = appIosDirectory.childFile('Podfile.lock');
......
...@@ -11,6 +11,7 @@ import '../application_package.dart'; ...@@ -11,6 +11,7 @@ import '../application_package.dart';
import '../base/common.dart'; import '../base/common.dart';
import '../base/context.dart'; import '../base/context.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../base/fingerprint.dart';
import '../base/io.dart'; import '../base/io.dart';
import '../base/logger.dart'; import '../base/logger.dart';
import '../base/os.dart'; import '../base/os.dart';
...@@ -193,7 +194,7 @@ Future<XcodeBuildResult> buildXcodeProject({ ...@@ -193,7 +194,7 @@ Future<XcodeBuildResult> buildXcodeProject({
bool codesign: true, bool codesign: true,
bool usesTerminalUi: true, bool usesTerminalUi: true,
}) async { }) async {
if (!await upgradePbxProjWithFlutterAssets(app.name)) if (!await upgradePbxProjWithFlutterAssets(app.name, app.appDirectory))
return new XcodeBuildResult(success: false); return new XcodeBuildResult(success: false);
if (!_checkXcodeVersion()) if (!_checkXcodeVersion())
...@@ -241,7 +242,6 @@ Future<XcodeBuildResult> buildXcodeProject({ ...@@ -241,7 +242,6 @@ Future<XcodeBuildResult> buildXcodeProject({
// copied over to a location that is suitable for Xcodebuild to find them. // copied over to a location that is suitable for Xcodebuild to find them.
final Directory appDirectory = fs.directory(app.appDirectory); final Directory appDirectory = fs.directory(app.appDirectory);
await _addServicesToBundle(appDirectory); await _addServicesToBundle(appDirectory);
final String previousGeneratedXcconfig = readGeneratedXcconfig(app.appDirectory);
updateGeneratedXcodeProperties( updateGeneratedXcodeProperties(
projectPath: fs.currentDirectory.path, projectPath: fs.currentDirectory.path,
...@@ -251,13 +251,26 @@ Future<XcodeBuildResult> buildXcodeProject({ ...@@ -251,13 +251,26 @@ Future<XcodeBuildResult> buildXcodeProject({
); );
if (hasPlugins()) { if (hasPlugins()) {
final String currentGeneratedXcconfig = readGeneratedXcconfig(app.appDirectory); final String iosPath = fs.path.join(fs.currentDirectory.path, app.appDirectory);
await cocoaPods.processPods( // If the Xcode project, Podfile, or Generated.xcconfig have changed since
// last run, pods should be updated.
final Fingerprinter fingerprinter = new Fingerprinter(
fingerprintPath: fs.path.join(getIosBuildDirectory(), 'pod_inputs.fingerprint'),
paths: <String>[
_getPbxProjPath(app.appDirectory),
fs.path.join(iosPath, 'Podfile'),
fs.path.join(iosPath, 'Flutter', 'Generated.xcconfig'),
],
properties: <String, String>{},
);
final bool didPodInstall = await cocoaPods.processPods(
appIosDirectory: appDirectory, appIosDirectory: appDirectory,
iosEngineDir: flutterFrameworkDir(buildInfo.mode), iosEngineDir: flutterFrameworkDir(buildInfo.mode),
isSwift: app.isSwift, isSwift: app.isSwift,
flutterPodChanged: previousGeneratedXcconfig != currentGeneratedXcconfig, dependenciesChanged: !await fingerprinter.doesFingerprintMatch()
); );
if (didPodInstall)
await fingerprinter.writeFingerprint();
} }
// If buildNumber is not specified, keep the project untouched. // If buildNumber is not specified, keep the project untouched.
...@@ -614,6 +627,9 @@ Future<Null> _copyServiceFrameworks(List<Map<String, String>> services, Director ...@@ -614,6 +627,9 @@ Future<Null> _copyServiceFrameworks(List<Map<String, String>> services, Director
} }
} }
/// The path of the Xcode project file.
String _getPbxProjPath(String appPath) => fs.path.join(fs.currentDirectory.path, appPath, 'Runner.xcodeproj', 'project.pbxproj');
void _copyServiceDefinitionsManifest(List<Map<String, String>> services, File manifest) { void _copyServiceDefinitionsManifest(List<Map<String, String>> services, File manifest) {
printTrace("Creating service definitions manifest at '${manifest.path}'"); printTrace("Creating service definitions manifest at '${manifest.path}'");
final List<Map<String, String>> jsonServices = services.map((Map<String, String> service) => <String, String>{ final List<Map<String, String>> jsonServices = services.map((Map<String, String> service) => <String, String>{
...@@ -626,9 +642,8 @@ void _copyServiceDefinitionsManifest(List<Map<String, String>> services, File ma ...@@ -626,9 +642,8 @@ void _copyServiceDefinitionsManifest(List<Map<String, String>> services, File ma
manifest.writeAsStringSync(json.encode(jsonObject), mode: FileMode.write, flush: true); manifest.writeAsStringSync(json.encode(jsonObject), mode: FileMode.write, flush: true);
} }
Future<bool> upgradePbxProjWithFlutterAssets(String app) async { Future<bool> upgradePbxProjWithFlutterAssets(String app, String appPath) async {
final File xcodeProjectFile = fs.file(fs.path.join('ios', 'Runner.xcodeproj', final File xcodeProjectFile = fs.file(_getPbxProjPath(appPath));
'project.pbxproj'));
assert(await xcodeProjectFile.exists()); assert(await xcodeProjectFile.exists());
final List<String> lines = await xcodeProjectFile.readAsLines(); final List<String> lines = await xcodeProjectFile.readAsLines();
...@@ -651,7 +666,7 @@ Future<bool> upgradePbxProjWithFlutterAssets(String app) async { ...@@ -651,7 +666,7 @@ Future<bool> upgradePbxProjWithFlutterAssets(String app) async {
if (!lines.contains(l1) || !lines.contains(l3) || if (!lines.contains(l1) || !lines.contains(l3) ||
!lines.contains(l5) || !lines.contains(l7)) { !lines.contains(l5) || !lines.contains(l7)) {
printError('Automatic upgrade of project.pbxproj failed.'); printError('Automatic upgrade of project.pbxproj failed.');
printError(' To manually upgrade, open ios/Runner.xcodeproj/project.pbxproj:'); printError(' To manually upgrade, open ${xcodeProjectFile.path}:');
printError(' Add the following line in the "PBXBuildFile" section'); printError(' Add the following line in the "PBXBuildFile" section');
printError(l2); printError(l2);
printError(' Add the following line in the "PBXFileReference" section'); printError(' Add the following line in the "PBXFileReference" section');
......
...@@ -128,7 +128,7 @@ void main() { ...@@ -128,7 +128,7 @@ void main() {
testUsingContext('prints error, if CocoaPods is not installed', () async { testUsingContext('prints error, if CocoaPods is not installed', () async {
projectUnderTest.childFile('Podfile').createSync(); projectUnderTest.childFile('Podfile').createSync();
cocoaPodsUnderTest = const TestCocoaPods(false); cocoaPodsUnderTest = const TestCocoaPods(false);
await cocoaPodsUnderTest.processPods( final bool didInstall = await cocoaPodsUnderTest.processPods(
appIosDirectory: projectUnderTest, appIosDirectory: projectUnderTest,
iosEngineDir: 'engine/path', iosEngineDir: 'engine/path',
); );
...@@ -139,6 +139,7 @@ void main() { ...@@ -139,6 +139,7 @@ void main() {
)); ));
expect(testLogger.errorText, contains('not installed')); expect(testLogger.errorText, contains('not installed'));
expect(testLogger.errorText, contains('Skipping pod install')); expect(testLogger.errorText, contains('Skipping pod install'));
expect(didInstall, isFalse);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => fs, FileSystem: () => fs,
ProcessManager: () => mockProcessManager, ProcessManager: () => mockProcessManager,
...@@ -221,11 +222,12 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by ...@@ -221,11 +222,12 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by
projectUnderTest.childFile('Pods/Manifest.lock') projectUnderTest.childFile('Pods/Manifest.lock')
..createSync(recursive: true) ..createSync(recursive: true)
..writeAsString('Existing lock file.'); ..writeAsString('Existing lock file.');
await cocoaPodsUnderTest.processPods( final bool didInstall = await cocoaPodsUnderTest.processPods(
appIosDirectory: projectUnderTest, appIosDirectory: projectUnderTest,
iosEngineDir: 'engine/path', iosEngineDir: 'engine/path',
flutterPodChanged: false, dependenciesChanged: false,
); );
expect(didInstall, isTrue);
verify(mockProcessManager.run( verify(mockProcessManager.run(
<String>['pod', 'install', '--verbose'], <String>['pod', 'install', '--verbose'],
workingDirectory: 'project/ios', workingDirectory: 'project/ios',
...@@ -243,11 +245,12 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by ...@@ -243,11 +245,12 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by
projectUnderTest.childFile('Podfile.lock') projectUnderTest.childFile('Podfile.lock')
..createSync() ..createSync()
..writeAsString('Existing lock file.'); ..writeAsString('Existing lock file.');
await cocoaPodsUnderTest.processPods( final bool didInstall = await cocoaPodsUnderTest.processPods(
appIosDirectory: projectUnderTest, appIosDirectory: projectUnderTest,
iosEngineDir: 'engine/path', iosEngineDir: 'engine/path',
flutterPodChanged: false, dependenciesChanged: false,
); );
expect(didInstall, isTrue);
verify(mockProcessManager.run( verify(mockProcessManager.run(
<String>['pod', 'install', '--verbose'], <String>['pod', 'install', '--verbose'],
workingDirectory: 'project/ios', workingDirectory: 'project/ios',
...@@ -271,11 +274,12 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by ...@@ -271,11 +274,12 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by
projectUnderTest.childFile('Pods/Manifest.lock') projectUnderTest.childFile('Pods/Manifest.lock')
..createSync(recursive: true) ..createSync(recursive: true)
..writeAsString('Different lock file.'); ..writeAsString('Different lock file.');
await cocoaPodsUnderTest.processPods( final bool didInstall = await cocoaPodsUnderTest.processPods(
appIosDirectory: projectUnderTest, appIosDirectory: projectUnderTest,
iosEngineDir: 'engine/path', iosEngineDir: 'engine/path',
flutterPodChanged: false, dependenciesChanged: false,
); );
expect(didInstall, isTrue);
verify(mockProcessManager.run( verify(mockProcessManager.run(
<String>['pod', 'install', '--verbose'], <String>['pod', 'install', '--verbose'],
workingDirectory: 'project/ios', workingDirectory: 'project/ios',
...@@ -299,11 +303,12 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by ...@@ -299,11 +303,12 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by
projectUnderTest.childFile('Pods/Manifest.lock') projectUnderTest.childFile('Pods/Manifest.lock')
..createSync(recursive: true) ..createSync(recursive: true)
..writeAsString('Existing lock file.'); ..writeAsString('Existing lock file.');
await cocoaPodsUnderTest.processPods( final bool didInstall = await cocoaPodsUnderTest.processPods(
appIosDirectory: projectUnderTest, appIosDirectory: projectUnderTest,
iosEngineDir: 'engine/path', iosEngineDir: 'engine/path',
flutterPodChanged: true, dependenciesChanged: true,
); );
expect(didInstall, isTrue);
verify(mockProcessManager.run( verify(mockProcessManager.run(
<String>['pod', 'install', '--verbose'], <String>['pod', 'install', '--verbose'],
workingDirectory: 'project/ios', workingDirectory: 'project/ios',
...@@ -327,11 +332,12 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by ...@@ -327,11 +332,12 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by
projectUnderTest.childFile('Pods/Manifest.lock') projectUnderTest.childFile('Pods/Manifest.lock')
..createSync(recursive: true) ..createSync(recursive: true)
..writeAsString('Existing lock file.'); ..writeAsString('Existing lock file.');
await cocoaPodsUnderTest.processPods( final bool didInstall = await cocoaPodsUnderTest.processPods(
appIosDirectory: projectUnderTest, appIosDirectory: projectUnderTest,
iosEngineDir: 'engine/path', iosEngineDir: 'engine/path',
flutterPodChanged: false, dependenciesChanged: false,
); );
expect(didInstall, isFalse);
verifyNever(mockProcessManager.run( verifyNever(mockProcessManager.run(
typed<List<String>>(any), typed<List<String>>(any),
workingDirectory: any, workingDirectory: any,
...@@ -370,7 +376,7 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by ...@@ -370,7 +376,7 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by
await cocoaPodsUnderTest.processPods( await cocoaPodsUnderTest.processPods(
appIosDirectory: projectUnderTest, appIosDirectory: projectUnderTest,
iosEngineDir: 'engine/path', iosEngineDir: 'engine/path',
flutterPodChanged: true, dependenciesChanged: true,
); );
fail('Tool throw expected when pod install fails'); fail('Tool throw expected when pod install fails');
} on ToolExit { } on ToolExit {
...@@ -406,4 +412,4 @@ final ProcessResult exitsHappy = new ProcessResult( ...@@ -406,4 +412,4 @@ final ProcessResult exitsHappy = new ProcessResult(
0, // exitCode 0, // exitCode
'', // stdout '', // stdout
'', // stderr '', // stderr
); );
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment