mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00

Due to changes in Xcode 15, several variables such as `DT_TOOLCHAIN_DIR` have been eliminateed in favour of others such as `TOOLCHAIN_DIR`. This broke CocoaPods support under Xcode 15, as reported in: https://github.com/CocoaPods/CocoaPods/issues/12012 @vashworth worked around this in Flutter in: https://github.com/flutter/flutter/pull/132803 The CocoaPods issue was resolved by the following PR to their repo: https://github.com/CocoaPods/CocoaPods/pull/12009 and was released in CocoaPods 1.13.0. Also switches from an if-else chain to a switch for CocoaPodsStatus handling. Related: https://github.com/flutter/flutter/issues/133584 Related: https://github.com/flutter/flutter/issues/132755 ## Pre-launch Checklist - [X] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [X] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [X] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [X] I signed the [CLA]. - [X] I listed at least one issue that this PR fixes in the description above. - [X] I updated/added relevant documentation (doc comments with `///`). - [X] I added new tests to check the change I am making, or this PR is [test-exempt]. - [X] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/wiki/Tree-hygiene#overview [Tree Hygiene]: https://github.com/flutter/flutter/wiki/Tree-hygiene [test-exempt]: https://github.com/flutter/flutter/wiki/Tree-hygiene#tests [Flutter Style Guide]: https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo [Features we expect every widget to implement]: https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/wiki/Tree-hygiene#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/wiki/Chat Co-authored-by: Greg Spencer <gspencergoog@users.noreply.github.com>
1397 lines
56 KiB
Dart
1397 lines
56 KiB
Dart
// 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.
|
|
|
|
import 'package:file/file.dart';
|
|
import 'package:file/memory.dart';
|
|
import 'package:flutter_tools/src/base/logger.dart';
|
|
import 'package:flutter_tools/src/base/platform.dart';
|
|
import 'package:flutter_tools/src/base/version.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 'package:unified_analytics/unified_analytics.dart';
|
|
|
|
import '../../src/common.dart';
|
|
import '../../src/context.dart';
|
|
import '../../src/fake_process_manager.dart';
|
|
import '../../src/fakes.dart';
|
|
|
|
enum _StdioStream {
|
|
stdout,
|
|
stderr,
|
|
}
|
|
|
|
void main() {
|
|
late FileSystem fileSystem;
|
|
late FakeProcessManager fakeProcessManager;
|
|
late CocoaPods cocoaPodsUnderTest;
|
|
late BufferLogger logger;
|
|
late TestUsage usage;
|
|
late FakeAnalytics fakeAnalytics;
|
|
|
|
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();
|
|
fakeAnalytics = getInitializedFakeAnalyticsInstance(
|
|
fs: fileSystem,
|
|
fakeFlutterVersion: FakeFlutterVersion(),
|
|
);
|
|
cocoaPodsUnderTest = CocoaPods(
|
|
fileSystem: fileSystem,
|
|
processManager: fakeProcessManager,
|
|
logger: logger,
|
|
platform: FakePlatform(operatingSystem: 'macos'),
|
|
xcodeProjectInterpreter: FakeXcodeProjectInterpreter(),
|
|
usage: usage,
|
|
analytics: fakeAnalytics,
|
|
);
|
|
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.9.0');
|
|
expect(await cocoaPodsUnderTest.evaluateCocoaPodsInstallation, CocoaPodsStatus.belowMinimumVersion);
|
|
});
|
|
|
|
testWithoutContext('detects below recommended version', () async {
|
|
pretendPodIsInstalled();
|
|
pretendPodVersionIs('1.12.5');
|
|
expect(await cocoaPodsUnderTest.evaluateCocoaPodsInstallation, CocoaPodsStatus.belowRecommendedVersion);
|
|
});
|
|
|
|
testWithoutContext('detects at recommended version', () async {
|
|
pretendPodIsInstalled();
|
|
pretendPodVersionIs('1.13.0');
|
|
expect(await cocoaPodsUnderTest.evaluateCocoaPodsInstallation, CocoaPodsStatus.recommended);
|
|
});
|
|
|
|
testWithoutContext('detects above recommended version', () async {
|
|
pretendPodIsInstalled();
|
|
pretendPodVersionIs('1.13.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,
|
|
analytics: fakeAnalytics,
|
|
);
|
|
|
|
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,
|
|
analytics: fakeAnalytics,
|
|
);
|
|
|
|
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,
|
|
// This output is the output that a real CocoaPods install would generate.
|
|
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"),
|
|
);
|
|
});
|
|
|
|
testUsingContext('throws if plugin requires higher minimum iOS version using "platform"', () async {
|
|
final FlutterProject projectUnderTest = setupProjectUnderTest();
|
|
pretendPodIsInstalled();
|
|
pretendPodVersionIs('100.0.0');
|
|
fileSystem.file(fileSystem.path.join('project', 'ios', 'Podfile'))
|
|
..createSync()
|
|
..writeAsStringSync('Existing Podfile');
|
|
const String fakePluginName = 'some_plugin';
|
|
final File podspec = projectUnderTest.ios.symlinks
|
|
.childDirectory('plugins')
|
|
.childDirectory(fakePluginName)
|
|
.childDirectory('ios')
|
|
.childFile('$fakePluginName.podspec');
|
|
podspec.createSync(recursive: true);
|
|
podspec.writeAsStringSync('''
|
|
Pod::Spec.new do |s|
|
|
s.name = '$fakePluginName'
|
|
s.version = '0.0.1'
|
|
s.summary = 'A plugin'
|
|
s.source_files = 'Classes/**/*.{h,m}'
|
|
s.dependency 'Flutter'
|
|
s.static_framework = true
|
|
s.platform = :ios, '15.0'
|
|
end''');
|
|
|
|
fakeProcessManager.addCommand(
|
|
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: _fakeHigherMinimumIOSVersionPodInstallOutput(fakePluginName),
|
|
),
|
|
);
|
|
|
|
await expectLater(cocoaPodsUnderTest.processPods(
|
|
xcodeProject: projectUnderTest.ios,
|
|
buildMode: BuildMode.debug,
|
|
), throwsToolExit());
|
|
expect(
|
|
logger.errorText,
|
|
contains(
|
|
'The plugin "$fakePluginName" requires a higher minimum iOS '
|
|
'deployment version than your application is targeting.'
|
|
),
|
|
);
|
|
// The error should contain specific instructions for fixing the build
|
|
// based on parsing the plugin's podspec.
|
|
expect(
|
|
logger.errorText,
|
|
contains(
|
|
"To build, increase your application's deployment target to at least "
|
|
'15.0 as described at https://docs.flutter.dev/deployment/ios'
|
|
),
|
|
);
|
|
});
|
|
|
|
testUsingContext('throws if plugin requires higher minimum iOS version using "deployment_target"', () async {
|
|
final FlutterProject projectUnderTest = setupProjectUnderTest();
|
|
pretendPodIsInstalled();
|
|
pretendPodVersionIs('100.0.0');
|
|
fileSystem.file(fileSystem.path.join('project', 'ios', 'Podfile'))
|
|
..createSync()
|
|
..writeAsStringSync('Existing Podfile');
|
|
const String fakePluginName = 'some_plugin';
|
|
final File podspec = projectUnderTest.ios.symlinks
|
|
.childDirectory('plugins')
|
|
.childDirectory(fakePluginName)
|
|
.childDirectory('ios')
|
|
.childFile('$fakePluginName.podspec');
|
|
podspec.createSync(recursive: true);
|
|
podspec.writeAsStringSync('''
|
|
Pod::Spec.new do |s|
|
|
s.name = '$fakePluginName'
|
|
s.version = '0.0.1'
|
|
s.summary = 'A plugin'
|
|
s.source_files = 'Classes/**/*.{h,m}'
|
|
s.dependency 'Flutter'
|
|
s.static_framework = true
|
|
s.ios.deployment_target = '15.0'
|
|
end''');
|
|
|
|
fakeProcessManager.addCommand(
|
|
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: _fakeHigherMinimumIOSVersionPodInstallOutput(fakePluginName),
|
|
),
|
|
);
|
|
|
|
await expectLater(cocoaPodsUnderTest.processPods(
|
|
xcodeProject: projectUnderTest.ios,
|
|
buildMode: BuildMode.debug,
|
|
), throwsToolExit());
|
|
expect(
|
|
logger.errorText,
|
|
contains(
|
|
'The plugin "$fakePluginName" requires a higher minimum iOS '
|
|
'deployment version than your application is targeting.'
|
|
),
|
|
);
|
|
// The error should contain specific instructions for fixing the build
|
|
// based on parsing the plugin's podspec.
|
|
expect(
|
|
logger.errorText,
|
|
contains(
|
|
"To build, increase your application's deployment target to at least "
|
|
'15.0 as described at https://docs.flutter.dev/deployment/ios'
|
|
),
|
|
);
|
|
});
|
|
|
|
testUsingContext('throws if plugin requires higher minimum iOS version with darwin layout', () async {
|
|
final FlutterProject projectUnderTest = setupProjectUnderTest();
|
|
pretendPodIsInstalled();
|
|
pretendPodVersionIs('100.0.0');
|
|
fileSystem.file(fileSystem.path.join('project', 'ios', 'Podfile'))
|
|
..createSync()
|
|
..writeAsStringSync('Existing Podfile');
|
|
const String fakePluginName = 'some_plugin';
|
|
final File podspec = projectUnderTest.ios.symlinks
|
|
.childDirectory('plugins')
|
|
.childDirectory(fakePluginName)
|
|
.childDirectory('darwin')
|
|
.childFile('$fakePluginName.podspec');
|
|
podspec.createSync(recursive: true);
|
|
podspec.writeAsStringSync('''
|
|
Pod::Spec.new do |s|
|
|
s.name = '$fakePluginName'
|
|
s.version = '0.0.1'
|
|
s.summary = 'A plugin'
|
|
s.source_files = 'Classes/**/*.{h,m}'
|
|
s.dependency 'Flutter'
|
|
s.static_framework = true
|
|
s.osx.deployment_target = '10.15'
|
|
s.ios.deployment_target = '15.0'
|
|
end''');
|
|
|
|
fakeProcessManager.addCommand(
|
|
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: _fakeHigherMinimumIOSVersionPodInstallOutput(fakePluginName, subdir: 'darwin'),
|
|
),
|
|
);
|
|
|
|
await expectLater(cocoaPodsUnderTest.processPods(
|
|
xcodeProject: projectUnderTest.ios,
|
|
buildMode: BuildMode.debug,
|
|
), throwsToolExit());
|
|
expect(
|
|
logger.errorText,
|
|
contains(
|
|
'The plugin "$fakePluginName" requires a higher minimum iOS '
|
|
'deployment version than your application is targeting.'
|
|
),
|
|
);
|
|
// The error should contain specific instructions for fixing the build
|
|
// based on parsing the plugin's podspec.
|
|
expect(
|
|
logger.errorText,
|
|
contains(
|
|
"To build, increase your application's deployment target to at least "
|
|
'15.0 as described at https://docs.flutter.dev/deployment/ios'
|
|
),
|
|
);
|
|
});
|
|
|
|
testUsingContext('throws if plugin requires unknown higher minimum iOS version', () async {
|
|
final FlutterProject projectUnderTest = setupProjectUnderTest();
|
|
pretendPodIsInstalled();
|
|
pretendPodVersionIs('100.0.0');
|
|
fileSystem.file(fileSystem.path.join('project', 'ios', 'Podfile'))
|
|
..createSync()
|
|
..writeAsStringSync('Existing Podfile');
|
|
const String fakePluginName = 'some_plugin';
|
|
final File podspec = projectUnderTest.ios.symlinks
|
|
.childDirectory('plugins')
|
|
.childDirectory(fakePluginName)
|
|
.childDirectory('ios')
|
|
.childFile('$fakePluginName.podspec');
|
|
podspec.createSync(recursive: true);
|
|
// It's very unlikely that someone would actually ever do anything like
|
|
// this, but arbitrary code is possible, so test that if it's not what
|
|
// the error handler parsing expects, a fallback is used.
|
|
podspec.writeAsStringSync('''
|
|
Pod::Spec.new do |s|
|
|
s.name = '$fakePluginName'
|
|
s.version = '0.0.1'
|
|
s.summary = 'A plugin'
|
|
s.source_files = 'Classes/**/*.{h,m}'
|
|
s.dependency 'Flutter'
|
|
s.static_framework = true
|
|
version_var = '15.0'
|
|
s.platform = :ios, version_var
|
|
end''');
|
|
|
|
fakeProcessManager.addCommand(
|
|
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: _fakeHigherMinimumIOSVersionPodInstallOutput(fakePluginName),
|
|
),
|
|
);
|
|
|
|
await expectLater(cocoaPodsUnderTest.processPods(
|
|
xcodeProject: projectUnderTest.ios,
|
|
buildMode: BuildMode.debug,
|
|
), throwsToolExit());
|
|
expect(
|
|
logger.errorText,
|
|
contains(
|
|
'The plugin "$fakePluginName" requires a higher minimum iOS '
|
|
'deployment version than your application is targeting.'
|
|
),
|
|
);
|
|
// The error should contain non-specific instructions for fixing the build
|
|
// and note that the minimum version could not be determined.
|
|
expect(
|
|
logger.errorText,
|
|
contains(
|
|
"To build, increase your application's deployment target as "
|
|
'described at https://docs.flutter.dev/deployment/ios',
|
|
),
|
|
);
|
|
expect(
|
|
logger.errorText,
|
|
contains(
|
|
'The minimum required version for "$fakePluginName" could not be '
|
|
'determined',
|
|
),
|
|
);
|
|
});
|
|
|
|
testUsingContext('throws if plugin has a dependency that requires a higher minimum iOS version', () 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,
|
|
// This is the (very slightly abridged) output from updating the
|
|
// minimum version of the GoogleMaps dependency in
|
|
// google_maps_flutter_ios without updating the minimum iOS version to
|
|
// match, as an example of a misconfigured plugin.
|
|
stdout: '''
|
|
Analyzing dependencies
|
|
|
|
Inspecting targets to integrate
|
|
Using `ARCHS` setting to build architectures of target `Pods-Runner`: (``)
|
|
Using `ARCHS` setting to build architectures of target `Pods-RunnerTests`: (``)
|
|
|
|
Fetching external sources
|
|
-> Fetching podspec for `Flutter` from `Flutter`
|
|
-> Fetching podspec for `google_maps_flutter_ios` from `.symlinks/plugins/google_maps_flutter_ios/ios`
|
|
|
|
Resolving dependencies of `Podfile`
|
|
CDN: trunk Relative path: CocoaPods-version.yml exists! Returning local because checking is only performed in repo update
|
|
CDN: trunk Relative path: Specs/a/d/d/GoogleMaps/8.0.0/GoogleMaps.podspec.json exists! Returning local because checking is only performed in repo update
|
|
[!] CocoaPods could not find compatible versions for pod "GoogleMaps":
|
|
In Podfile:
|
|
google_maps_flutter_ios (from `.symlinks/plugins/google_maps_flutter_ios/ios`) was resolved to 0.0.1, which depends on
|
|
GoogleMaps (~> 8.0)
|
|
|
|
Specs satisfying the `GoogleMaps (~> 8.0)` dependency were found, but they required a higher minimum deployment target.''',
|
|
),
|
|
);
|
|
|
|
await expectLater(cocoaPodsUnderTest.processPods(
|
|
xcodeProject: projectUnderTest.ios,
|
|
buildMode: BuildMode.debug,
|
|
), throwsToolExit());
|
|
expect(
|
|
logger.errorText,
|
|
contains(
|
|
'The pod "GoogleMaps" required by the plugin "google_maps_flutter_ios" '
|
|
"requires a higher minimum iOS deployment version than the plugin's "
|
|
'reported minimum version.'
|
|
),
|
|
);
|
|
// The error should tell the user to contact the plugin author, as this
|
|
// case is hard for us to give exact advice on, and should only be
|
|
// possible if there's a mistake in the plugin's podspec.
|
|
expect(
|
|
logger.errorText,
|
|
contains(
|
|
'To build, remove the plugin "google_maps_flutter_ios", or contact '
|
|
"the plugin's developers for assistance.",
|
|
),
|
|
);
|
|
});
|
|
|
|
testUsingContext('throws if plugin requires higher minimum macOS version using "platform"', () async {
|
|
final FlutterProject projectUnderTest = setupProjectUnderTest();
|
|
pretendPodIsInstalled();
|
|
pretendPodVersionIs('100.0.0');
|
|
fileSystem.file(fileSystem.path.join('project', 'macos', 'Podfile'))
|
|
..createSync()
|
|
..writeAsStringSync('Existing Podfile');
|
|
const String fakePluginName = 'some_plugin';
|
|
final File podspec = projectUnderTest.macos.ephemeralDirectory
|
|
.childDirectory('.symlinks')
|
|
.childDirectory('plugins')
|
|
.childDirectory(fakePluginName)
|
|
.childDirectory('macos')
|
|
.childFile('$fakePluginName.podspec');
|
|
podspec.createSync(recursive: true);
|
|
podspec.writeAsStringSync('''
|
|
Pod::Spec.new do |spec|
|
|
spec.name = '$fakePluginName'
|
|
spec.version = '0.0.1'
|
|
spec.summary = 'A plugin'
|
|
spec.source_files = 'Classes/**/*.swift'
|
|
spec.dependency 'FlutterMacOS'
|
|
spec.static_framework = true
|
|
spec.platform = :osx, "12.7"
|
|
end''');
|
|
|
|
fakeProcessManager.addCommand(
|
|
FakeCommand(
|
|
command: const <String>['pod', 'install', '--verbose'],
|
|
workingDirectory: 'project/macos',
|
|
environment: const <String, String>{
|
|
'COCOAPODS_DISABLE_STATS': 'true',
|
|
'LANG': 'en_US.UTF-8',
|
|
},
|
|
exitCode: 1,
|
|
stdout: _fakeHigherMinimumMacOSVersionPodInstallOutput(fakePluginName),
|
|
),
|
|
);
|
|
|
|
await expectLater(cocoaPodsUnderTest.processPods(
|
|
xcodeProject: projectUnderTest.macos,
|
|
buildMode: BuildMode.debug,
|
|
), throwsToolExit());
|
|
expect(
|
|
logger.errorText,
|
|
contains(
|
|
'The plugin "$fakePluginName" requires a higher minimum macOS '
|
|
'deployment version than your application is targeting.'
|
|
),
|
|
);
|
|
// The error should contain specific instructions for fixing the build
|
|
// based on parsing the plugin's podspec.
|
|
expect(
|
|
logger.errorText,
|
|
contains(
|
|
"To build, increase your application's deployment target to at least "
|
|
'12.7 as described at https://docs.flutter.dev/deployment/macos'
|
|
),
|
|
);
|
|
});
|
|
|
|
testUsingContext('throws if plugin requires higher minimum macOS version using "deployment_target"', () async {
|
|
final FlutterProject projectUnderTest = setupProjectUnderTest();
|
|
pretendPodIsInstalled();
|
|
pretendPodVersionIs('100.0.0');
|
|
fileSystem.file(fileSystem.path.join('project', 'macos', 'Podfile'))
|
|
..createSync()
|
|
..writeAsStringSync('Existing Podfile');
|
|
const String fakePluginName = 'some_plugin';
|
|
final File podspec = projectUnderTest.macos.ephemeralDirectory
|
|
.childDirectory('.symlinks')
|
|
.childDirectory('plugins')
|
|
.childDirectory(fakePluginName)
|
|
.childDirectory('macos')
|
|
.childFile('$fakePluginName.podspec');
|
|
podspec.createSync(recursive: true);
|
|
podspec.writeAsStringSync('''
|
|
Pod::Spec.new do |spec|
|
|
spec.name = '$fakePluginName'
|
|
spec.version = '0.0.1'
|
|
spec.summary = 'A plugin'
|
|
spec.source_files = 'Classes/**/*.{h,m}'
|
|
spec.dependency 'Flutter'
|
|
spec.static_framework = true
|
|
spec.osx.deployment_target = '12.7'
|
|
end''');
|
|
|
|
fakeProcessManager.addCommand(
|
|
FakeCommand(
|
|
command: const <String>['pod', 'install', '--verbose'],
|
|
workingDirectory: 'project/macos',
|
|
environment: const <String, String>{
|
|
'COCOAPODS_DISABLE_STATS': 'true',
|
|
'LANG': 'en_US.UTF-8',
|
|
},
|
|
exitCode: 1,
|
|
stdout: _fakeHigherMinimumMacOSVersionPodInstallOutput(fakePluginName),
|
|
),
|
|
);
|
|
|
|
await expectLater(cocoaPodsUnderTest.processPods(
|
|
xcodeProject: projectUnderTest.macos,
|
|
buildMode: BuildMode.debug,
|
|
), throwsToolExit());
|
|
expect(
|
|
logger.errorText,
|
|
contains(
|
|
'The plugin "$fakePluginName" requires a higher minimum macOS '
|
|
'deployment version than your application is targeting.'
|
|
),
|
|
);
|
|
// The error should contain specific instructions for fixing the build
|
|
// based on parsing the plugin's podspec.
|
|
expect(
|
|
logger.errorText,
|
|
contains(
|
|
"To build, increase your application's deployment target to at least "
|
|
'12.7 as described at https://docs.flutter.dev/deployment/macos'
|
|
),
|
|
);
|
|
});
|
|
|
|
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",
|
|
'bus error': '/Library/Ruby/Gems/2.6.0/gems/ffi-1.15.5/lib/ffi/library.rb:275: [BUG] Bus Error at 0x000000010072c000',
|
|
};
|
|
possibleErrors.forEach((String errorName, String cocoaPodsError) {
|
|
void testToolExitsWithCocoapodsMessage(_StdioStream outputStream) {
|
|
final String streamName = outputStream == _StdioStream.stdout ? 'stdout' : 'stderr';
|
|
testUsingContext('ffi $errorName failure to $streamName 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: outputStream == _StdioStream.stdout ? cocoaPodsError : '',
|
|
stderr: outputStream == _StdioStream.stderr ? 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(
|
|
logger.errorText,
|
|
contains('enable-libffi-alloc'),
|
|
);
|
|
expect(usage.events, contains(const TestUsageEvent('pod-install-failure', 'arm-ffi')));
|
|
expect(fakeAnalytics.sentEvents, contains(Event.appleUsageEvent(workflow: 'pod-install-failure', parameter: 'arm-ffi')));
|
|
});
|
|
}
|
|
testToolExitsWithCocoapodsMessage(_StdioStream.stdout);
|
|
testToolExitsWithCocoapodsMessage(_StdioStream.stderr);
|
|
});
|
|
|
|
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,
|
|
);
|
|
expect(didInstall, isTrue);
|
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
|
expect(logger.traceText, contains('CocoaPods Pods-Runner-frameworks.sh script not found'));
|
|
});
|
|
|
|
testUsingContext('runs CocoaPods Pod runner script migrator', () 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.');
|
|
projectUnderTest.ios.podRunnerFrameworksScript
|
|
..createSync(recursive: true)
|
|
..writeAsStringSync(r'source="$(readlink "${source}")"');
|
|
|
|
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 CocoaPods cocoaPodsUnderTestXcode143 = CocoaPods(
|
|
fileSystem: fileSystem,
|
|
processManager: fakeProcessManager,
|
|
logger: logger,
|
|
platform: FakePlatform(operatingSystem: 'macos'),
|
|
xcodeProjectInterpreter: XcodeProjectInterpreter.test(processManager: fakeProcessManager, version: Version(14, 3, 0)),
|
|
usage: usage,
|
|
analytics: fakeAnalytics,
|
|
);
|
|
|
|
final bool didInstall = await cocoaPodsUnderTestXcode143.processPods(
|
|
xcodeProject: projectUnderTest.ios,
|
|
buildMode: BuildMode.debug,
|
|
);
|
|
expect(didInstall, isTrue);
|
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
|
// Now has readlink -f flag.
|
|
expect(projectUnderTest.ios.podRunnerFrameworksScript.readAsStringSync(), contains(r'source="$(readlink -f "${source}")"'));
|
|
expect(logger.statusText, contains('Upgrading Pods-Runner-frameworks.sh'));
|
|
});
|
|
|
|
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);
|
|
});
|
|
});
|
|
}
|
|
|
|
String _fakeHigherMinimumIOSVersionPodInstallOutput(String fakePluginName, {String subdir = 'ios'}) {
|
|
return '''
|
|
Preparing
|
|
|
|
Analyzing dependencies
|
|
|
|
Inspecting targets to integrate
|
|
Using `ARCHS` setting to build architectures of target `Pods-Runner`: (``)
|
|
Using `ARCHS` setting to build architectures of target `Pods-RunnerTests`: (``)
|
|
|
|
Fetching external sources
|
|
-> Fetching podspec for `Flutter` from `Flutter`
|
|
-> Fetching podspec for `$fakePluginName` from `.symlinks/plugins/$fakePluginName/$subdir`
|
|
-> Fetching podspec for `another_plugin` from `.symlinks/plugins/another_plugin/ios`
|
|
|
|
Resolving dependencies of `Podfile`
|
|
CDN: trunk Relative path: CocoaPods-version.yml exists! Returning local because checking is only performed in repo update
|
|
[!] CocoaPods could not find compatible versions for pod "$fakePluginName":
|
|
In Podfile:
|
|
$fakePluginName (from `.symlinks/plugins/$fakePluginName/$subdir`)
|
|
|
|
Specs satisfying the `$fakePluginName (from `.symlinks/plugins/$fakePluginName/subdir`)` dependency were found, but they required a higher minimum deployment target.''';
|
|
}
|
|
|
|
String _fakeHigherMinimumMacOSVersionPodInstallOutput(String fakePluginName, {String subdir = 'macos'}) {
|
|
return '''
|
|
Preparing
|
|
|
|
Analyzing dependencies
|
|
|
|
Inspecting targets to integrate
|
|
Using `ARCHS` setting to build architectures of target `Pods-Runner`: (``)
|
|
Using `ARCHS` setting to build architectures of target `Pods-RunnerTests`: (``)
|
|
|
|
Fetching external sources
|
|
-> Fetching podspec for `FlutterMacOS` from `Flutter/ephemeral`
|
|
-> Fetching podspec for `$fakePluginName` from `Flutter/ephemeral/.symlinks/plugins/$fakePluginName/$subdir`
|
|
-> Fetching podspec for `another_plugin` from `Flutter/ephemeral/.symlinks/plugins/another_plugin/macos`
|
|
|
|
Resolving dependencies of `Podfile`
|
|
CDN: trunk Relative path: CocoaPods-version.yml exists! Returning local because checking is only performed in repo update
|
|
[!] CocoaPods could not find compatible versions for pod "$fakePluginName":
|
|
In Podfile:
|
|
$fakePluginName (from `Flutter/ephemeral/.symlinks/plugins/$fakePluginName/$subdir`)
|
|
|
|
Specs satisfying the `$fakePluginName (from `Flutter/ephemeral/.symlinks/plugins/$fakePluginName/$subdir`)` dependency were found, but they required a higher minimum deployment target.''';
|
|
}
|
|
|
|
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;
|
|
}
|