blob: b49ea1fabccd9e9d815f2bf237416b98babd190f [file] [log] [blame]
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart = 2.8
import 'package:file/file.dart';
import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/flutter_plugins.dart';
import 'package:flutter_tools/src/ios/xcodeproj.dart';
import 'package:flutter_tools/src/macos/cocoapods.dart';
import 'package:flutter_tools/src/project.dart';
import 'package:flutter_tools/src/reporting/reporting.dart';
import 'package:test/fake.dart';
import '../../src/common.dart';
import '../../src/context.dart';
import '../../src/fake_process_manager.dart';
typedef InvokeProcess = Future<ProcessResult> Function();
void main() {
FileSystem fileSystem;
FakeProcessManager fakeProcessManager;
CocoaPods cocoaPodsUnderTest;
BufferLogger logger;
TestUsage usage;
void pretendPodVersionFails() {
fakeProcessManager.addCommand(
const FakeCommand(
command: <String>['pod', '--version'],
exitCode: 1,
),
);
}
void pretendPodVersionIs(String versionText) {
fakeProcessManager.addCommand(
FakeCommand(
command: const <String>['pod', '--version'],
stdout: versionText,
),
);
}
void podsIsInHomeDir() {
fileSystem.directory(fileSystem.path.join(
'.cocoapods',
'repos',
'master',
)).createSync(recursive: true);
}
FlutterProject setupProjectUnderTest() {
// This needs to be run within testWithoutContext and not setUp since FlutterProject uses context.
final FlutterProject projectUnderTest = FlutterProject.fromDirectory(fileSystem.directory('project'));
projectUnderTest.ios.xcodeProject.createSync(recursive: true);
projectUnderTest.macos.xcodeProject.createSync(recursive: true);
return projectUnderTest;
}
setUp(() async {
Cache.flutterRoot = 'flutter';
fileSystem = MemoryFileSystem.test();
fakeProcessManager = FakeProcessManager.empty();
logger = BufferLogger.test();
usage = TestUsage();
cocoaPodsUnderTest = CocoaPods(
fileSystem: fileSystem,
processManager: fakeProcessManager,
logger: logger,
platform: FakePlatform(operatingSystem: 'macos'),
xcodeProjectInterpreter: FakeXcodeProjectInterpreter(),
usage: usage,
);
fileSystem.file(fileSystem.path.join(
Cache.flutterRoot, 'packages', 'flutter_tools', 'templates', 'cocoapods', 'Podfile-ios-objc',
))
..createSync(recursive: true)
..writeAsStringSync('Objective-C iOS podfile template');
fileSystem.file(fileSystem.path.join(
Cache.flutterRoot, 'packages', 'flutter_tools', 'templates', 'cocoapods', 'Podfile-ios-swift',
))
..createSync(recursive: true)
..writeAsStringSync('Swift iOS podfile template');
fileSystem.file(fileSystem.path.join(
Cache.flutterRoot, 'packages', 'flutter_tools', 'templates', 'cocoapods', 'Podfile-macos',
))
..createSync(recursive: true)
..writeAsStringSync('macOS podfile template');
});
void pretendPodIsNotInstalled() {
fakeProcessManager.addCommand(
const FakeCommand(
command: <String>['which', 'pod'],
exitCode: 1,
),
);
}
void pretendPodIsBroken() {
fakeProcessManager.addCommands(<FakeCommand>[
// it is present
const FakeCommand(
command: <String>['which', 'pod'],
),
// but is not working
const FakeCommand(
command: <String>['pod', '--version'],
exitCode: 1,
),
]);
}
void pretendPodIsInstalled() {
fakeProcessManager.addCommands(<FakeCommand>[
const FakeCommand(
command: <String>['which', 'pod'],
),
]);
}
group('Evaluate installation', () {
testWithoutContext('detects not installed, if pod exec does not exist', () async {
pretendPodIsNotInstalled();
expect(await cocoaPodsUnderTest.evaluateCocoaPodsInstallation, CocoaPodsStatus.notInstalled);
});
testWithoutContext('detects not installed, if pod is installed but version fails', () async {
pretendPodIsInstalled();
pretendPodVersionFails();
expect(await cocoaPodsUnderTest.evaluateCocoaPodsInstallation, CocoaPodsStatus.brokenInstall);
});
testWithoutContext('detects installed', () async {
pretendPodIsInstalled();
pretendPodVersionIs('0.0.1');
expect(await cocoaPodsUnderTest.evaluateCocoaPodsInstallation, isNot(CocoaPodsStatus.notInstalled));
});
testWithoutContext('detects unknown version', () async {
pretendPodIsInstalled();
pretendPodVersionIs('Plugin loaded.\n1.5.3');
expect(await cocoaPodsUnderTest.evaluateCocoaPodsInstallation, CocoaPodsStatus.unknownVersion);
});
testWithoutContext('detects below minimum version', () async {
pretendPodIsInstalled();
pretendPodVersionIs('1.6.0');
expect(await cocoaPodsUnderTest.evaluateCocoaPodsInstallation, CocoaPodsStatus.belowMinimumVersion);
});
testWithoutContext('detects below recommended version', () async {
pretendPodIsInstalled();
pretendPodVersionIs('1.9.0');
expect(await cocoaPodsUnderTest.evaluateCocoaPodsInstallation, CocoaPodsStatus.belowRecommendedVersion);
});
testWithoutContext('detects at recommended version', () async {
pretendPodIsInstalled();
pretendPodVersionIs('1.11.0');
expect(await cocoaPodsUnderTest.evaluateCocoaPodsInstallation, CocoaPodsStatus.recommended);
});
testWithoutContext('detects above recommended version', () async {
pretendPodIsInstalled();
pretendPodVersionIs('1.11.1');
expect(await cocoaPodsUnderTest.evaluateCocoaPodsInstallation, CocoaPodsStatus.recommended);
});
});
group('Setup Podfile', () {
testUsingContext('creates objective-c Podfile when not present', () async {
final FlutterProject projectUnderTest = setupProjectUnderTest();
await cocoaPodsUnderTest.setupPodfile(projectUnderTest.ios);
expect(projectUnderTest.ios.podfile.readAsStringSync(), 'Objective-C iOS podfile template');
});
testUsingContext('creates swift Podfile if swift', () async {
final FlutterProject projectUnderTest = setupProjectUnderTest();
final FakeXcodeProjectInterpreter fakeXcodeProjectInterpreter = FakeXcodeProjectInterpreter(buildSettings: <String, String>{
'SWIFT_VERSION': '5.0',
});
final CocoaPods cocoaPodsUnderTest = CocoaPods(
fileSystem: fileSystem,
processManager: fakeProcessManager,
logger: logger,
platform: FakePlatform(operatingSystem: 'macos'),
xcodeProjectInterpreter: fakeXcodeProjectInterpreter,
usage: usage,
);
final FlutterProject project = FlutterProject.fromDirectoryTest(fileSystem.directory('project'));
await cocoaPodsUnderTest.setupPodfile(project.ios);
expect(projectUnderTest.ios.podfile.readAsStringSync(), 'Swift iOS podfile template');
});
testUsingContext('creates macOS Podfile when not present', () async {
final FlutterProject projectUnderTest = setupProjectUnderTest();
projectUnderTest.macos.xcodeProject.createSync(recursive: true);
await cocoaPodsUnderTest.setupPodfile(projectUnderTest.macos);
expect(projectUnderTest.macos.podfile.readAsStringSync(), 'macOS podfile template');
});
testUsingContext('does not recreate Podfile when already present', () async {
final FlutterProject projectUnderTest = setupProjectUnderTest();
projectUnderTest.ios.podfile..createSync()..writeAsStringSync('Existing Podfile');
final FlutterProject project = FlutterProject.fromDirectoryTest(fileSystem.directory('project'));
await cocoaPodsUnderTest.setupPodfile(project.ios);
expect(projectUnderTest.ios.podfile.readAsStringSync(), 'Existing Podfile');
});
testUsingContext('does not create Podfile when we cannot interpret Xcode projects', () async {
final FlutterProject projectUnderTest = setupProjectUnderTest();
final CocoaPods cocoaPodsUnderTest = CocoaPods(
fileSystem: fileSystem,
processManager: fakeProcessManager,
logger: logger,
platform: FakePlatform(operatingSystem: 'macos'),
xcodeProjectInterpreter: FakeXcodeProjectInterpreter(isInstalled: false),
usage: usage,
);
final FlutterProject project = FlutterProject.fromDirectoryTest(fileSystem.directory('project'));
await cocoaPodsUnderTest.setupPodfile(project.ios);
expect(projectUnderTest.ios.podfile.existsSync(), false);
});
testUsingContext('includes Pod config in xcconfig files, if not present', () async {
final FlutterProject projectUnderTest = setupProjectUnderTest();
projectUnderTest.ios.podfile..createSync()..writeAsStringSync('Existing Podfile');
projectUnderTest.ios.xcodeConfigFor('Debug')
..createSync(recursive: true)
..writeAsStringSync('Existing debug config');
projectUnderTest.ios.xcodeConfigFor('Release')
..createSync(recursive: true)
..writeAsStringSync('Existing release config');
final FlutterProject project = FlutterProject.fromDirectoryTest(fileSystem.directory('project'));
await cocoaPodsUnderTest.setupPodfile(project.ios);
final String debugContents = projectUnderTest.ios.xcodeConfigFor('Debug').readAsStringSync();
expect(debugContents, contains(
'#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"\n'));
expect(debugContents, contains('Existing debug config'));
final String releaseContents = projectUnderTest.ios.xcodeConfigFor('Release').readAsStringSync();
expect(releaseContents, contains(
'#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"\n'));
expect(releaseContents, contains('Existing release config'));
});
testUsingContext('does not include Pod config in xcconfig files, if legacy non-option include present', () async {
final FlutterProject projectUnderTest = setupProjectUnderTest();
projectUnderTest.ios.podfile..createSync()..writeAsStringSync('Existing Podfile');
const String legacyDebugInclude = '#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig';
projectUnderTest.ios.xcodeConfigFor('Debug')
..createSync(recursive: true)
..writeAsStringSync(legacyDebugInclude);
const String legacyReleaseInclude = '#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig';
projectUnderTest.ios.xcodeConfigFor('Release')
..createSync(recursive: true)
..writeAsStringSync(legacyReleaseInclude);
final FlutterProject project = FlutterProject.fromDirectoryTest(fileSystem.directory('project'));
await cocoaPodsUnderTest.setupPodfile(project.ios);
final String debugContents = projectUnderTest.ios.xcodeConfigFor('Debug').readAsStringSync();
// Redundant contains check, but this documents what we're testing--that the optional
// #include? doesn't get written in addition to the previous style #include.
expect(debugContents, isNot(contains('#include?')));
expect(debugContents, equals(legacyDebugInclude));
final String releaseContents = projectUnderTest.ios.xcodeConfigFor('Release').readAsStringSync();
expect(releaseContents, isNot(contains('#include?')));
expect(releaseContents, equals(legacyReleaseInclude));
});
testUsingContext('does not include Pod config in xcconfig files, if flavor include present', () async {
final FlutterProject projectUnderTest = setupProjectUnderTest();
projectUnderTest.ios.podfile..createSync()..writeAsStringSync('Existing Podfile');
const String flavorDebugInclude = '#include? "Pods/Target Support Files/Pods-Free App/Pods-Free App.debug free.xcconfig"';
projectUnderTest.ios.xcodeConfigFor('Debug')
..createSync(recursive: true)
..writeAsStringSync(flavorDebugInclude);
const String flavorReleaseInclude = '#include? "Pods/Target Support Files/Pods-Free App/Pods-Free App.release free.xcconfig"';
projectUnderTest.ios.xcodeConfigFor('Release')
..createSync(recursive: true)
..writeAsStringSync(flavorReleaseInclude);
final FlutterProject project = FlutterProject.fromDirectoryTest(fileSystem.directory('project'));
await cocoaPodsUnderTest.setupPodfile(project.ios);
final String debugContents = projectUnderTest.ios.xcodeConfigFor('Debug').readAsStringSync();
// Redundant contains check, but this documents what we're testing--that the optional
// #include? doesn't get written in addition to the previous style #include.
expect(debugContents, isNot(contains('Pods-Runner/Pods-Runner.debug')));
expect(debugContents, equals(flavorDebugInclude));
final String releaseContents = projectUnderTest.ios.xcodeConfigFor('Release').readAsStringSync();
expect(releaseContents, isNot(contains('Pods-Runner/Pods-Runner.release')));
expect(releaseContents, equals(flavorReleaseInclude));
});
});
group('Update xcconfig', () {
testUsingContext('includes Pod config in xcconfig files, if the user manually added Pod dependencies without using Flutter plugins', () async {
final FlutterProject projectUnderTest = setupProjectUnderTest();
fileSystem.file(fileSystem.path.join('project', 'foo', '.packages'))
..createSync(recursive: true)
..writeAsStringSync('\n');
projectUnderTest.ios.podfile..createSync()..writeAsStringSync('Custom Podfile');
projectUnderTest.ios.podfileLock..createSync()..writeAsStringSync('Podfile.lock from user executed `pod install`');
projectUnderTest.packagesFile..createSync()..writeAsStringSync('');
projectUnderTest.ios.xcodeConfigFor('Debug')
..createSync(recursive: true)
..writeAsStringSync('Existing debug config');
projectUnderTest.ios.xcodeConfigFor('Release')
..createSync(recursive: true)
..writeAsStringSync('Existing release config');
final FlutterProject project = FlutterProject.fromDirectoryTest(fileSystem.directory('project'));
await injectPlugins(project, iosPlatform: true);
final String debugContents = projectUnderTest.ios.xcodeConfigFor('Debug').readAsStringSync();
expect(debugContents, contains(
'#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"\n'));
expect(debugContents, contains('Existing debug config'));
final String releaseContents = projectUnderTest.ios.xcodeConfigFor('Release').readAsStringSync();
expect(releaseContents, contains(
'#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"\n'));
expect(releaseContents, contains('Existing release config'));
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(),
});
});
group('Process pods', () {
setUp(() {
podsIsInHomeDir();
});
testUsingContext('throwsToolExit if CocoaPods is not installed', () async {
final FlutterProject projectUnderTest = setupProjectUnderTest();
pretendPodIsNotInstalled();
projectUnderTest.ios.podfile.createSync();
await expectLater(cocoaPodsUnderTest.processPods(
xcodeProject: projectUnderTest.ios,
buildMode: BuildMode.debug,
), throwsToolExit(message: 'CocoaPods not installed or not in valid state'));
expect(fakeProcessManager, hasNoRemainingExpectations);
expect(fakeProcessManager, hasNoRemainingExpectations);
});
testUsingContext('throwsToolExit if CocoaPods install is broken', () async {
final FlutterProject projectUnderTest = setupProjectUnderTest();
pretendPodIsBroken();
projectUnderTest.ios.podfile.createSync();
await expectLater(cocoaPodsUnderTest.processPods(
xcodeProject: projectUnderTest.ios,
buildMode: BuildMode.debug,
), throwsToolExit(message: 'CocoaPods not installed or not in valid state'));
expect(fakeProcessManager, hasNoRemainingExpectations);
expect(fakeProcessManager, hasNoRemainingExpectations);
});
testUsingContext('exits if Podfile creates the Flutter engine symlink', () async {
final FlutterProject projectUnderTest = setupProjectUnderTest();
fileSystem.file(fileSystem.path.join('project', 'ios', 'Podfile'))
..createSync()
..writeAsStringSync('Existing Podfile');
final Directory symlinks = projectUnderTest.ios.symlinks
..createSync(recursive: true);
symlinks.childLink('flutter').createSync('cache');
await expectLater(cocoaPodsUnderTest.processPods(
xcodeProject: projectUnderTest.ios,
buildMode: BuildMode.debug,
), throwsToolExit(message: 'Podfile is out of date'));
expect(fakeProcessManager, hasNoRemainingExpectations);
});
testUsingContext('exits if iOS Podfile parses .flutter-plugins', () async {
final FlutterProject projectUnderTest = setupProjectUnderTest();
fileSystem.file(fileSystem.path.join('project', 'ios', 'Podfile'))
..createSync()
..writeAsStringSync("plugin_pods = parse_KV_file('../.flutter-plugins')");
await expectLater(cocoaPodsUnderTest.processPods(
xcodeProject: projectUnderTest.ios,
buildMode: BuildMode.debug,
), throwsToolExit(message: 'Podfile is out of date'));
expect(fakeProcessManager, hasNoRemainingExpectations);
});
testUsingContext('prints warning if macOS Podfile parses .flutter-plugins', () async {
final FlutterProject projectUnderTest = setupProjectUnderTest();
pretendPodIsInstalled();
pretendPodVersionIs('100.0.0');
fakeProcessManager.addCommands(const <FakeCommand>[
FakeCommand(
command: <String>['pod', 'install', '--verbose'],
),
FakeCommand(
command: <String>['touch', 'project/macos/Podfile.lock'],
),
]);
projectUnderTest.macos.podfile
..createSync()
..writeAsStringSync("plugin_pods = parse_KV_file('../.flutter-plugins')");
projectUnderTest.macos.podfileLock
..createSync()
..writeAsStringSync('Existing lock file.');
await cocoaPodsUnderTest.processPods(
xcodeProject: projectUnderTest.macos,
buildMode: BuildMode.debug,
);
expect(logger.warningText, contains('Warning: Podfile is out of date'));
expect(logger.warningText, contains('rm macos/Podfile'));
expect(fakeProcessManager, hasNoRemainingExpectations);
});
testUsingContext('throws, if Podfile is missing.', () async {
final FlutterProject projectUnderTest = setupProjectUnderTest();
await expectLater(cocoaPodsUnderTest.processPods(
xcodeProject: projectUnderTest.ios,
buildMode: BuildMode.debug,
), throwsToolExit(message: 'Podfile missing'));
expect(fakeProcessManager, hasNoRemainingExpectations);
});
testUsingContext('throws, if specs repo is outdated.', () async {
final FlutterProject projectUnderTest = setupProjectUnderTest();
pretendPodIsInstalled();
pretendPodVersionIs('100.0.0');
fileSystem.file(fileSystem.path.join('project', 'ios', 'Podfile'))
..createSync()
..writeAsStringSync('Existing Podfile');
fakeProcessManager.addCommand(
const FakeCommand(
command: <String>['pod', 'install', '--verbose'],
workingDirectory: 'project/ios',
environment: <String, String>{
'COCOAPODS_DISABLE_STATS': 'true',
'LANG': 'en_US.UTF-8',
},
exitCode: 1,
stdout: '''
[!] Unable to satisfy the following requirements:
- `Firebase/Auth` required by `Podfile`
- `Firebase/Auth (= 4.0.0)` required by `Podfile.lock`
None of your spec sources contain a spec satisfying the dependencies: `Firebase/Auth, Firebase/Auth (= 4.0.0)`.
You have either:
* out-of-date source repos which you can update with `pod repo update` or with `pod install --repo-update`.
* mistyped the name or version.
* not added the source repo that hosts the Podspec to your Podfile.
Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by default.''',
),
);
await expectLater(cocoaPodsUnderTest.processPods(
xcodeProject: projectUnderTest.ios,
buildMode: BuildMode.debug,
), throwsToolExit());
expect(
logger.errorText,
contains(
"CocoaPods's specs repository is too out-of-date to satisfy dependencies"),
);
});
final Map<String, String> possibleErrors = <String, String>{
'symbol not found': 'LoadError - dlsym(0x7fbbeb6837d0, Init_ffi_c): symbol not found - /Library/Ruby/Gems/2.6.0/gems/ffi-1.13.1/lib/ffi_c.bundle',
'incompatible architecture': "LoadError - (mach-o file, but is an incompatible architecture (have 'arm64', need 'x86_64')), '/usr/lib/ffi_c.bundle' (no such file) - /Library/Ruby/Gems/2.6.0/gems/ffi-1.15.4/lib/ffi_c.bundle",
};
possibleErrors.forEach((String errorName, String cocoaPodsError) {
testUsingContext('ffi $errorName failure on ARM macOS prompts gem install', () async {
final FlutterProject projectUnderTest = setupProjectUnderTest();
pretendPodIsInstalled();
pretendPodVersionIs('100.0.0');
fileSystem.file(fileSystem.path.join('project', 'ios', 'Podfile'))
..createSync()
..writeAsStringSync('Existing Podfile');
fakeProcessManager.addCommands(<FakeCommand>[
FakeCommand(
command: const <String>['pod', 'install', '--verbose'],
workingDirectory: 'project/ios',
environment: const <String, String>{
'COCOAPODS_DISABLE_STATS': 'true',
'LANG': 'en_US.UTF-8',
},
exitCode: 1,
stdout: cocoaPodsError,
),
const FakeCommand(
command: <String>['which', 'sysctl'],
),
const FakeCommand(
command: <String>['sysctl', 'hw.optional.arm64'],
stdout: 'hw.optional.arm64: 1',
),
]);
await expectToolExitLater(
cocoaPodsUnderTest.processPods(
xcodeProject: projectUnderTest.ios,
buildMode: BuildMode.debug,
),
equals('Error running pod install'),
);
expect(
logger.errorText,
contains('set up CocoaPods for ARM macOS'),
);
expect(usage.events, contains(const TestUsageEvent('pod-install-failure', 'arm-ffi')));
});
});
testUsingContext('ffi failure on x86 macOS does not prompt gem install', () async {
final FlutterProject projectUnderTest = setupProjectUnderTest();
pretendPodIsInstalled();
pretendPodVersionIs('100.0.0');
fileSystem.file(fileSystem.path.join('project', 'ios', 'Podfile'))
..createSync()
..writeAsStringSync('Existing Podfile');
fakeProcessManager.addCommands(<FakeCommand>[
const FakeCommand(
command: <String>['pod', 'install', '--verbose'],
workingDirectory: 'project/ios',
environment: <String, String>{
'COCOAPODS_DISABLE_STATS': 'true',
'LANG': 'en_US.UTF-8',
},
exitCode: 1,
stderr: 'LoadError - dlsym(0x7fbbeb6837d0, Init_ffi_c): symbol not found - /Library/Ruby/Gems/2.6.0/gems/ffi-1.13.1/lib/ffi_c.bundle',
),
const FakeCommand(
command: <String>['which', 'sysctl'],
),
const FakeCommand(
command: <String>['sysctl', 'hw.optional.arm64'],
exitCode: 1,
),
]);
// Capture Usage.test() events.
final StringBuffer buffer =
await capturedConsolePrint(() => expectToolExitLater(
cocoaPodsUnderTest.processPods(
xcodeProject: projectUnderTest.ios,
buildMode: BuildMode.debug,
),
equals('Error running pod install'),
));
expect(
logger.errorText,
isNot(contains('ARM macOS')),
);
expect(buffer.isEmpty, true);
});
testUsingContext('run pod install, if Podfile.lock is missing', () async {
final FlutterProject projectUnderTest = setupProjectUnderTest();
pretendPodIsInstalled();
pretendPodVersionIs('100.0.0');
projectUnderTest.ios.podfile
..createSync()
..writeAsStringSync('Existing Podfile');
projectUnderTest.ios.podManifestLock
..createSync(recursive: true)
..writeAsStringSync('Existing lock file.');
fakeProcessManager.addCommands(const <FakeCommand>[
FakeCommand(
command: <String>['pod', 'install', '--verbose'],
workingDirectory: 'project/ios',
environment: <String, String>{'COCOAPODS_DISABLE_STATS': 'true', 'LANG': 'en_US.UTF-8'},
),
]);
final bool didInstall = await cocoaPodsUnderTest.processPods(
xcodeProject: projectUnderTest.ios,
buildMode: BuildMode.debug,
dependenciesChanged: false,
);
expect(didInstall, isTrue);
expect(fakeProcessManager, hasNoRemainingExpectations);
});
testUsingContext('runs iOS pod install, if Manifest.lock is missing', () async {
final FlutterProject projectUnderTest = setupProjectUnderTest();
pretendPodIsInstalled();
pretendPodVersionIs('100.0.0');
projectUnderTest.ios.podfile
..createSync()
..writeAsStringSync('Existing Podfile');
projectUnderTest.ios.podfileLock
..createSync()
..writeAsStringSync('Existing lock file.');
fakeProcessManager.addCommands(const <FakeCommand>[
FakeCommand(
command: <String>['pod', 'install', '--verbose'],
workingDirectory: 'project/ios',
environment: <String, String>{'COCOAPODS_DISABLE_STATS': 'true', 'LANG': 'en_US.UTF-8'},
),
FakeCommand(
command: <String>['touch', 'project/ios/Podfile.lock'],
),
]);
final bool didInstall = await cocoaPodsUnderTest.processPods(
xcodeProject: projectUnderTest.ios,
buildMode: BuildMode.debug,
dependenciesChanged: false,
);
expect(didInstall, isTrue);
expect(fakeProcessManager, hasNoRemainingExpectations);
});
testUsingContext('runs macOS pod install, if Manifest.lock is missing', () async {
final FlutterProject projectUnderTest = setupProjectUnderTest();
pretendPodIsInstalled();
pretendPodVersionIs('100.0.0');
projectUnderTest.macos.podfile
..createSync()
..writeAsStringSync('Existing Podfile');
projectUnderTest.macos.podfileLock
..createSync()
..writeAsStringSync('Existing lock file.');
fakeProcessManager.addCommands(const <FakeCommand>[
FakeCommand(
command: <String>['pod', 'install', '--verbose'],
workingDirectory: 'project/macos',
environment: <String, String>{'COCOAPODS_DISABLE_STATS': 'true', 'LANG': 'en_US.UTF-8'},
),
FakeCommand(
command: <String>['touch', 'project/macos/Podfile.lock'],
),
]);
final bool didInstall = await cocoaPodsUnderTest.processPods(
xcodeProject: projectUnderTest.macos,
buildMode: BuildMode.debug,
dependenciesChanged: false,
);
expect(didInstall, isTrue);
expect(fakeProcessManager, hasNoRemainingExpectations);
});
testUsingContext('runs pod install, if Manifest.lock different from Podspec.lock', () async {
final FlutterProject projectUnderTest = setupProjectUnderTest();
pretendPodIsInstalled();
pretendPodVersionIs('100.0.0');
projectUnderTest.ios.podfile
..createSync()
..writeAsStringSync('Existing Podfile');
projectUnderTest.ios.podfileLock
..createSync()
..writeAsStringSync('Existing lock file.');
projectUnderTest.ios.podManifestLock
..createSync(recursive: true)
..writeAsStringSync('Different lock file.');
fakeProcessManager.addCommands(const <FakeCommand>[
FakeCommand(
command: <String>['pod', 'install', '--verbose'],
workingDirectory: 'project/ios',
environment: <String, String>{'COCOAPODS_DISABLE_STATS': 'true', 'LANG': 'en_US.UTF-8'},
),
FakeCommand(
command: <String>['touch', 'project/ios/Podfile.lock'],
),
]);
final bool didInstall = await cocoaPodsUnderTest.processPods(
xcodeProject: projectUnderTest.ios,
buildMode: BuildMode.debug,
dependenciesChanged: false,
);
expect(didInstall, isTrue);
expect(fakeProcessManager, hasNoRemainingExpectations);
});
testUsingContext('runs pod install, if flutter framework changed', () async {
final FlutterProject projectUnderTest = setupProjectUnderTest();
pretendPodIsInstalled();
pretendPodVersionIs('100.0.0');
projectUnderTest.ios.podfile
..createSync()
..writeAsStringSync('Existing Podfile');
projectUnderTest.ios.podfileLock
..createSync()
..writeAsStringSync('Existing lock file.');
projectUnderTest.ios.podManifestLock
..createSync(recursive: true)
..writeAsStringSync('Existing lock file.');
fakeProcessManager.addCommands(const <FakeCommand>[
FakeCommand(
command: <String>['pod', 'install', '--verbose'],
workingDirectory: 'project/ios',
environment: <String, String>{'COCOAPODS_DISABLE_STATS': 'true', 'LANG': 'en_US.UTF-8'},
),
FakeCommand(
command: <String>['touch', 'project/ios/Podfile.lock'],
),
]);
final bool didInstall = await cocoaPodsUnderTest.processPods(
xcodeProject: projectUnderTest.ios,
buildMode: BuildMode.debug,
dependenciesChanged: true,
);
expect(didInstall, isTrue);
expect(fakeProcessManager, hasNoRemainingExpectations);
});
testUsingContext('runs pod install, if Podfile.lock is older than Podfile', () async {
final FlutterProject projectUnderTest = setupProjectUnderTest();
pretendPodIsInstalled();
pretendPodVersionIs('100.0.0');
projectUnderTest.ios.podfile
..createSync()
..writeAsStringSync('Existing Podfile');
projectUnderTest.ios.podfileLock
..createSync()
..writeAsStringSync('Existing lock file.');
projectUnderTest.ios.podManifestLock
..createSync(recursive: true)
..writeAsStringSync('Existing lock file.');
await Future<void>.delayed(const Duration(milliseconds: 10));
projectUnderTest.ios.podfile
.writeAsStringSync('Updated Podfile');
fakeProcessManager.addCommands(const <FakeCommand>[
FakeCommand(
command: <String>['pod', 'install', '--verbose'],
workingDirectory: 'project/ios',
environment: <String, String>{'COCOAPODS_DISABLE_STATS': 'true', 'LANG': 'en_US.UTF-8'},
),
FakeCommand(
command: <String>['touch', 'project/ios/Podfile.lock'],
),
]);
await cocoaPodsUnderTest.processPods(
xcodeProject: projectUnderTest.ios,
buildMode: BuildMode.debug,
dependenciesChanged: false,
);
expect(fakeProcessManager, hasNoRemainingExpectations);
});
testUsingContext('skips pod install, if nothing changed', () async {
final FlutterProject projectUnderTest = setupProjectUnderTest();
projectUnderTest.ios.podfile
..createSync()
..writeAsStringSync('Existing Podfile');
projectUnderTest.ios.podfileLock
..createSync()
..writeAsStringSync('Existing lock file.');
projectUnderTest.ios.podManifestLock
..createSync(recursive: true)
..writeAsStringSync('Existing lock file.');
final bool didInstall = await cocoaPodsUnderTest.processPods(
xcodeProject: projectUnderTest.ios,
buildMode: BuildMode.debug,
dependenciesChanged: false,
);
expect(didInstall, isFalse);
expect(fakeProcessManager, hasNoRemainingExpectations);
});
testUsingContext('a failed pod install deletes Pods/Manifest.lock', () async {
final FlutterProject projectUnderTest = setupProjectUnderTest();
pretendPodIsInstalled();
pretendPodVersionIs('100.0.0');
projectUnderTest.ios.podfile
..createSync()
..writeAsStringSync('Existing Podfile');
projectUnderTest.ios.podfileLock
..createSync()
..writeAsStringSync('Existing lock file.');
projectUnderTest.ios.podManifestLock
..createSync(recursive: true)
..writeAsStringSync('Existing lock file.');
fakeProcessManager.addCommand(
const FakeCommand(
command: <String>['pod', 'install', '--verbose'],
workingDirectory: 'project/ios',
environment: <String, String>{'COCOAPODS_DISABLE_STATS': 'true', 'LANG': 'en_US.UTF-8'},
exitCode: 1,
),
);
await expectLater(cocoaPodsUnderTest.processPods(
xcodeProject: projectUnderTest.ios,
buildMode: BuildMode.debug,
), throwsToolExit(message: 'Error running pod install'));
expect(projectUnderTest.ios.podManifestLock.existsSync(), isFalse);
});
});
}
class FakeXcodeProjectInterpreter extends Fake implements XcodeProjectInterpreter {
FakeXcodeProjectInterpreter({this.isInstalled = true, this.buildSettings = const <String, String>{}});
@override
final bool isInstalled;
@override
Future<Map<String, String>> getBuildSettings(
String projectPath, {
XcodeProjectBuildContext buildContext,
Duration timeout = const Duration(minutes: 1),
}) async => buildSettings;
final Map<String, String> buildSettings;
}