mirror of
https://github.com/flutter/packages.git
synced 2025-06-19 22:03:33 +08:00
[tool] Check for search paths in Swift plugins (#6954)
* Rename command, bump version * Update tests to write actual podspecs * Add new check * Analyzer fix * Unhdo file move
This commit is contained in:
@ -1,3 +1,9 @@
|
|||||||
|
## 0.13.3
|
||||||
|
|
||||||
|
* Renames `podspecs` to `podspec-check`. The old name will continue to work.
|
||||||
|
* Adds validation of the Swift-in-Obj-C-projects workaround in the podspecs of
|
||||||
|
iOS plugin implementations that use Swift.
|
||||||
|
|
||||||
## 0.13.2+1
|
## 0.13.2+1
|
||||||
|
|
||||||
* Replaces deprecated `flutter format` with `dart format` in `format`
|
* Replaces deprecated `flutter format` with `dart format` in `format`
|
||||||
|
@ -21,10 +21,10 @@ import 'fix_command.dart';
|
|||||||
import 'format_command.dart';
|
import 'format_command.dart';
|
||||||
import 'license_check_command.dart';
|
import 'license_check_command.dart';
|
||||||
import 'lint_android_command.dart';
|
import 'lint_android_command.dart';
|
||||||
import 'lint_podspecs_command.dart';
|
|
||||||
import 'list_command.dart';
|
import 'list_command.dart';
|
||||||
import 'make_deps_path_based_command.dart';
|
import 'make_deps_path_based_command.dart';
|
||||||
import 'native_test_command.dart';
|
import 'native_test_command.dart';
|
||||||
|
import 'podspec_check_command.dart';
|
||||||
import 'publish_check_command.dart';
|
import 'publish_check_command.dart';
|
||||||
import 'publish_command.dart';
|
import 'publish_command.dart';
|
||||||
import 'pubspec_check_command.dart';
|
import 'pubspec_check_command.dart';
|
||||||
@ -66,7 +66,7 @@ void main(List<String> args) {
|
|||||||
..addCommand(FormatCommand(packagesDir))
|
..addCommand(FormatCommand(packagesDir))
|
||||||
..addCommand(LicenseCheckCommand(packagesDir))
|
..addCommand(LicenseCheckCommand(packagesDir))
|
||||||
..addCommand(LintAndroidCommand(packagesDir))
|
..addCommand(LintAndroidCommand(packagesDir))
|
||||||
..addCommand(LintPodspecsCommand(packagesDir))
|
..addCommand(PodspecCheckCommand(packagesDir))
|
||||||
..addCommand(ListCommand(packagesDir))
|
..addCommand(ListCommand(packagesDir))
|
||||||
..addCommand(NativeTestCommand(packagesDir))
|
..addCommand(NativeTestCommand(packagesDir))
|
||||||
..addCommand(MakeDepsPathBasedCommand(packagesDir))
|
..addCommand(MakeDepsPathBasedCommand(packagesDir))
|
||||||
|
@ -6,7 +6,6 @@ import 'dart:convert';
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:file/file.dart';
|
import 'package:file/file.dart';
|
||||||
import 'package:path/path.dart' as p;
|
|
||||||
import 'package:platform/platform.dart';
|
import 'package:platform/platform.dart';
|
||||||
|
|
||||||
import 'common/core.dart';
|
import 'common/core.dart';
|
||||||
@ -20,23 +19,24 @@ const int _exitPodNotInstalled = 3;
|
|||||||
/// Lint the CocoaPod podspecs and run unit tests.
|
/// Lint the CocoaPod podspecs and run unit tests.
|
||||||
///
|
///
|
||||||
/// See https://guides.cocoapods.org/terminal/commands.html#pod_lib_lint.
|
/// See https://guides.cocoapods.org/terminal/commands.html#pod_lib_lint.
|
||||||
class LintPodspecsCommand extends PackageLoopingCommand {
|
class PodspecCheckCommand extends PackageLoopingCommand {
|
||||||
/// Creates an instance of the linter command.
|
/// Creates an instance of the linter command.
|
||||||
LintPodspecsCommand(
|
PodspecCheckCommand(
|
||||||
Directory packagesDir, {
|
Directory packagesDir, {
|
||||||
ProcessRunner processRunner = const ProcessRunner(),
|
ProcessRunner processRunner = const ProcessRunner(),
|
||||||
Platform platform = const LocalPlatform(),
|
Platform platform = const LocalPlatform(),
|
||||||
}) : super(packagesDir, processRunner: processRunner, platform: platform);
|
}) : super(packagesDir, processRunner: processRunner, platform: platform);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final String name = 'podspecs';
|
final String name = 'podspec-check';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<String> get aliases => <String>['podspec'];
|
List<String> get aliases => <String>['podspec', 'podspecs'];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final String description =
|
final String description =
|
||||||
'Runs "pod lib lint" on all iOS and macOS plugin podspecs.\n\n'
|
'Runs "pod lib lint" on all iOS and macOS plugin podspecs, as well as '
|
||||||
|
'making sure the podspecs follow repository standards.\n\n'
|
||||||
'This command requires "pod" and "flutter" to be in your path. Runs on macOS only.';
|
'This command requires "pod" and "flutter" to be in your path. Runs on macOS only.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -69,9 +69,32 @@ class LintPodspecsCommand extends PackageLoopingCommand {
|
|||||||
|
|
||||||
for (final File podspec in podspecs) {
|
for (final File podspec in podspecs) {
|
||||||
if (!await _lintPodspec(podspec)) {
|
if (!await _lintPodspec(podspec)) {
|
||||||
errors.add(p.basename(podspec.path));
|
errors.add(podspec.basename);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (await _hasIOSSwiftCode(package)) {
|
||||||
|
print('iOS Swift code found, checking for search paths settings...');
|
||||||
|
for (final File podspec in podspecs) {
|
||||||
|
if (_isPodspecMissingSearchPaths(podspec)) {
|
||||||
|
const String workaroundBlock = r'''
|
||||||
|
s.xcconfig = {
|
||||||
|
'LIBRARY_SEARCH_PATHS' => '$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)/ $(SDKROOT)/usr/lib/swift',
|
||||||
|
'LD_RUNPATH_SEARCH_PATHS' => '/usr/lib/swift',
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
final String path =
|
||||||
|
getRelativePosixPath(podspec, from: package.directory);
|
||||||
|
printError('$path is missing seach path configuration. Any iOS '
|
||||||
|
'plugin implementation that contains Swift implementation code '
|
||||||
|
'needs to contain the following:\n\n'
|
||||||
|
'$workaroundBlock\n'
|
||||||
|
'For more details, see https://github.com/flutter/flutter/issues/118418.');
|
||||||
|
errors.add(podspec.basename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return errors.isEmpty
|
return errors.isEmpty
|
||||||
? PackageResult.success()
|
? PackageResult.success()
|
||||||
: PackageResult.fail(errors);
|
: PackageResult.fail(errors);
|
||||||
@ -92,7 +115,7 @@ class LintPodspecsCommand extends PackageLoopingCommand {
|
|||||||
// Do not run the static analyzer on plugins with known analyzer issues.
|
// Do not run the static analyzer on plugins with known analyzer issues.
|
||||||
final String podspecPath = podspec.path;
|
final String podspecPath = podspec.path;
|
||||||
|
|
||||||
final String podspecBasename = p.basename(podspecPath);
|
final String podspecBasename = podspec.basename;
|
||||||
print('Linting $podspecBasename');
|
print('Linting $podspecBasename');
|
||||||
|
|
||||||
// Lint plugin as framework (use_frameworks!).
|
// Lint plugin as framework (use_frameworks!).
|
||||||
@ -126,4 +149,46 @@ class LintPodspecsCommand extends PackageLoopingCommand {
|
|||||||
return processRunner.run('pod', arguments,
|
return processRunner.run('pod', arguments,
|
||||||
workingDir: packagesDir, stdoutEncoding: utf8, stderrEncoding: utf8);
|
workingDir: packagesDir, stdoutEncoding: utf8, stderrEncoding: utf8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if there is any iOS plugin implementation code written in
|
||||||
|
/// Swift.
|
||||||
|
Future<bool> _hasIOSSwiftCode(RepositoryPackage package) async {
|
||||||
|
return getFilesForPackage(package).any((File entity) {
|
||||||
|
final String relativePath =
|
||||||
|
getRelativePosixPath(entity, from: package.directory);
|
||||||
|
// Ignore example code.
|
||||||
|
if (relativePath.startsWith('example/')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final String filePath = entity.path;
|
||||||
|
return path.extension(filePath) == '.swift';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if [podspec] could apply to iOS, but does not have the
|
||||||
|
/// workaround for search paths that makes Swift plugins build correctly in
|
||||||
|
/// Objective-C applications. See
|
||||||
|
/// https://github.com/flutter/flutter/issues/118418 for context and details.
|
||||||
|
///
|
||||||
|
/// This does not check that the plugin has Swift code, and thus whether the
|
||||||
|
/// workaround is needed, only whether or not it is there.
|
||||||
|
bool _isPodspecMissingSearchPaths(File podspec) {
|
||||||
|
final String directory = podspec.parent.basename;
|
||||||
|
// All macOS Flutter apps are Swift, so macOS-only podspecs don't need the
|
||||||
|
// workaround. If it's anywhere other than macos/, err or the side of
|
||||||
|
// assuming it's required.
|
||||||
|
if (directory == 'macos') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This errs on the side of being too strict, to minimize the chance of
|
||||||
|
// accidental incorrect configuration. If we ever need more flexibility
|
||||||
|
// due to a false negative we can adjust this as necessary.
|
||||||
|
final RegExp workaround = RegExp(r'''
|
||||||
|
\s*s\.(?:ios\.)?xcconfig = {[^}]*
|
||||||
|
\s*'LIBRARY_SEARCH_PATHS' => '\$\(TOOLCHAIN_DIR\)/usr/lib/swift/\$\(PLATFORM_NAME\)/ \$\(SDKROOT\)/usr/lib/swift',
|
||||||
|
\s*'LD_RUNPATH_SEARCH_PATHS' => '/usr/lib/swift',[^}]*
|
||||||
|
\s*}''', dotAll: true);
|
||||||
|
return !workaround.hasMatch(podspec.readAsStringSync());
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
name: flutter_plugin_tools
|
name: flutter_plugin_tools
|
||||||
description: Productivity utils for flutter/plugins and flutter/packages
|
description: Productivity utils for flutter/plugins and flutter/packages
|
||||||
repository: https://github.com/flutter/plugins/tree/main/script/tool
|
repository: https://github.com/flutter/plugins/tree/main/script/tool
|
||||||
version: 0.13.2+1
|
version: 0.13.3
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
args: ^2.1.0
|
args: ^2.1.0
|
||||||
|
@ -1,222 +0,0 @@
|
|||||||
// Copyright 2013 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 'dart:io' as io;
|
|
||||||
|
|
||||||
import 'package:args/command_runner.dart';
|
|
||||||
import 'package:file/file.dart';
|
|
||||||
import 'package:file/memory.dart';
|
|
||||||
import 'package:flutter_plugin_tools/src/common/core.dart';
|
|
||||||
import 'package:flutter_plugin_tools/src/lint_podspecs_command.dart';
|
|
||||||
import 'package:test/test.dart';
|
|
||||||
|
|
||||||
import 'mocks.dart';
|
|
||||||
import 'util.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
group('$LintPodspecsCommand', () {
|
|
||||||
FileSystem fileSystem;
|
|
||||||
late Directory packagesDir;
|
|
||||||
late CommandRunner<void> runner;
|
|
||||||
late MockPlatform mockPlatform;
|
|
||||||
late RecordingProcessRunner processRunner;
|
|
||||||
|
|
||||||
setUp(() {
|
|
||||||
fileSystem = MemoryFileSystem();
|
|
||||||
packagesDir = createPackagesDirectory(fileSystem: fileSystem);
|
|
||||||
|
|
||||||
mockPlatform = MockPlatform(isMacOS: true);
|
|
||||||
processRunner = RecordingProcessRunner();
|
|
||||||
final LintPodspecsCommand command = LintPodspecsCommand(
|
|
||||||
packagesDir,
|
|
||||||
processRunner: processRunner,
|
|
||||||
platform: mockPlatform,
|
|
||||||
);
|
|
||||||
|
|
||||||
runner =
|
|
||||||
CommandRunner<void>('podspec_test', 'Test for $LintPodspecsCommand');
|
|
||||||
runner.addCommand(command);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('only runs on macOS', () async {
|
|
||||||
createFakePlugin('plugin1', packagesDir,
|
|
||||||
extraFiles: <String>['plugin1.podspec']);
|
|
||||||
mockPlatform.isMacOS = false;
|
|
||||||
|
|
||||||
Error? commandError;
|
|
||||||
final List<String> output = await runCapturingPrint(
|
|
||||||
runner, <String>['podspecs'], errorHandler: (Error e) {
|
|
||||||
commandError = e;
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(commandError, isA<ToolExit>());
|
|
||||||
|
|
||||||
expect(
|
|
||||||
processRunner.recordedCalls,
|
|
||||||
equals(<ProcessCall>[]),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(
|
|
||||||
output,
|
|
||||||
containsAllInOrder(
|
|
||||||
<Matcher>[contains('only supported on macOS')],
|
|
||||||
));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('runs pod lib lint on a podspec', () async {
|
|
||||||
final RepositoryPackage plugin = createFakePlugin(
|
|
||||||
'plugin1',
|
|
||||||
packagesDir,
|
|
||||||
extraFiles: <String>[
|
|
||||||
'ios/plugin1.podspec',
|
|
||||||
'bogus.dart', // Ignore non-podspecs.
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
processRunner.mockProcessesForExecutable['pod'] = <io.Process>[
|
|
||||||
MockProcess(stdout: 'Foo', stderr: 'Bar'),
|
|
||||||
MockProcess(),
|
|
||||||
];
|
|
||||||
|
|
||||||
final List<String> output =
|
|
||||||
await runCapturingPrint(runner, <String>['podspecs']);
|
|
||||||
|
|
||||||
expect(
|
|
||||||
processRunner.recordedCalls,
|
|
||||||
orderedEquals(<ProcessCall>[
|
|
||||||
ProcessCall('which', const <String>['pod'], packagesDir.path),
|
|
||||||
ProcessCall(
|
|
||||||
'pod',
|
|
||||||
<String>[
|
|
||||||
'lib',
|
|
||||||
'lint',
|
|
||||||
plugin
|
|
||||||
.platformDirectory(FlutterPlatform.ios)
|
|
||||||
.childFile('plugin1.podspec')
|
|
||||||
.path,
|
|
||||||
'--configuration=Debug',
|
|
||||||
'--skip-tests',
|
|
||||||
'--use-modular-headers',
|
|
||||||
'--use-libraries'
|
|
||||||
],
|
|
||||||
packagesDir.path),
|
|
||||||
ProcessCall(
|
|
||||||
'pod',
|
|
||||||
<String>[
|
|
||||||
'lib',
|
|
||||||
'lint',
|
|
||||||
plugin
|
|
||||||
.platformDirectory(FlutterPlatform.ios)
|
|
||||||
.childFile('plugin1.podspec')
|
|
||||||
.path,
|
|
||||||
'--configuration=Debug',
|
|
||||||
'--skip-tests',
|
|
||||||
'--use-modular-headers',
|
|
||||||
],
|
|
||||||
packagesDir.path),
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(output, contains('Linting plugin1.podspec'));
|
|
||||||
expect(output, contains('Foo'));
|
|
||||||
expect(output, contains('Bar'));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('fails if pod is missing', () async {
|
|
||||||
createFakePlugin('plugin1', packagesDir,
|
|
||||||
extraFiles: <String>['plugin1.podspec']);
|
|
||||||
|
|
||||||
// Simulate failure from `which pod`.
|
|
||||||
processRunner.mockProcessesForExecutable['which'] = <io.Process>[
|
|
||||||
MockProcess(exitCode: 1),
|
|
||||||
];
|
|
||||||
|
|
||||||
Error? commandError;
|
|
||||||
final List<String> output = await runCapturingPrint(
|
|
||||||
runner, <String>['podspecs'], errorHandler: (Error e) {
|
|
||||||
commandError = e;
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(commandError, isA<ToolExit>());
|
|
||||||
|
|
||||||
expect(
|
|
||||||
output,
|
|
||||||
containsAllInOrder(
|
|
||||||
<Matcher>[
|
|
||||||
contains('Unable to find "pod". Make sure it is in your path.'),
|
|
||||||
],
|
|
||||||
));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('fails if linting as a framework fails', () async {
|
|
||||||
createFakePlugin('plugin1', packagesDir,
|
|
||||||
extraFiles: <String>['plugin1.podspec']);
|
|
||||||
|
|
||||||
// Simulate failure from `pod`.
|
|
||||||
processRunner.mockProcessesForExecutable['pod'] = <io.Process>[
|
|
||||||
MockProcess(exitCode: 1),
|
|
||||||
];
|
|
||||||
|
|
||||||
Error? commandError;
|
|
||||||
final List<String> output = await runCapturingPrint(
|
|
||||||
runner, <String>['podspecs'], errorHandler: (Error e) {
|
|
||||||
commandError = e;
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(commandError, isA<ToolExit>());
|
|
||||||
|
|
||||||
expect(
|
|
||||||
output,
|
|
||||||
containsAllInOrder(
|
|
||||||
<Matcher>[
|
|
||||||
contains('The following packages had errors:'),
|
|
||||||
contains('plugin1:\n'
|
|
||||||
' plugin1.podspec')
|
|
||||||
],
|
|
||||||
));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('fails if linting as a static library fails', () async {
|
|
||||||
createFakePlugin('plugin1', packagesDir,
|
|
||||||
extraFiles: <String>['plugin1.podspec']);
|
|
||||||
|
|
||||||
// Simulate failure from the second call to `pod`.
|
|
||||||
processRunner.mockProcessesForExecutable['pod'] = <io.Process>[
|
|
||||||
MockProcess(),
|
|
||||||
MockProcess(exitCode: 1),
|
|
||||||
];
|
|
||||||
|
|
||||||
Error? commandError;
|
|
||||||
final List<String> output = await runCapturingPrint(
|
|
||||||
runner, <String>['podspecs'], errorHandler: (Error e) {
|
|
||||||
commandError = e;
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(commandError, isA<ToolExit>());
|
|
||||||
|
|
||||||
expect(
|
|
||||||
output,
|
|
||||||
containsAllInOrder(
|
|
||||||
<Matcher>[
|
|
||||||
contains('The following packages had errors:'),
|
|
||||||
contains('plugin1:\n'
|
|
||||||
' plugin1.podspec')
|
|
||||||
],
|
|
||||||
));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('skips when there are no podspecs', () async {
|
|
||||||
createFakePlugin('plugin1', packagesDir);
|
|
||||||
|
|
||||||
final List<String> output =
|
|
||||||
await runCapturingPrint(runner, <String>['podspecs']);
|
|
||||||
|
|
||||||
expect(
|
|
||||||
output,
|
|
||||||
containsAllInOrder(
|
|
||||||
<Matcher>[contains('SKIPPING: No podspecs.')],
|
|
||||||
));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
428
script/tool/test/podspec_check_command_test.dart
Normal file
428
script/tool/test/podspec_check_command_test.dart
Normal file
@ -0,0 +1,428 @@
|
|||||||
|
// Copyright 2013 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 'dart:io' as io;
|
||||||
|
|
||||||
|
import 'package:args/command_runner.dart';
|
||||||
|
import 'package:file/file.dart';
|
||||||
|
import 'package:file/memory.dart';
|
||||||
|
import 'package:flutter_plugin_tools/src/common/core.dart';
|
||||||
|
import 'package:flutter_plugin_tools/src/podspec_check_command.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
import 'mocks.dart';
|
||||||
|
import 'util.dart';
|
||||||
|
|
||||||
|
/// Adds a fake podspec to [plugin]'s [platform] directory.
|
||||||
|
///
|
||||||
|
/// If [includeSwiftWorkaround] is set, the xcconfig additions to make Swift
|
||||||
|
/// libraries work in apps that have no Swift will be included. If
|
||||||
|
/// [scopeSwiftWorkaround] is set, it will be specific to the iOS configuration.
|
||||||
|
void _writeFakePodspec(RepositoryPackage plugin, String platform,
|
||||||
|
{bool includeSwiftWorkaround = false, bool scopeSwiftWorkaround = false}) {
|
||||||
|
final String pluginName = plugin.directory.basename;
|
||||||
|
final File file = plugin.directory
|
||||||
|
.childDirectory(platform)
|
||||||
|
.childFile('$pluginName.podspec');
|
||||||
|
final String swiftWorkaround = includeSwiftWorkaround
|
||||||
|
? '''
|
||||||
|
s.${scopeSwiftWorkaround ? 'ios.' : ''}xcconfig = {
|
||||||
|
'LIBRARY_SEARCH_PATHS' => '\$(TOOLCHAIN_DIR)/usr/lib/swift/\$(PLATFORM_NAME)/ \$(SDKROOT)/usr/lib/swift',
|
||||||
|
'LD_RUNPATH_SEARCH_PATHS' => '/usr/lib/swift',
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
: '';
|
||||||
|
file.createSync(recursive: true);
|
||||||
|
file.writeAsStringSync('''
|
||||||
|
#
|
||||||
|
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html
|
||||||
|
#
|
||||||
|
Pod::Spec.new do |s|
|
||||||
|
s.name = 'shared_preferences_foundation'
|
||||||
|
s.version = '0.0.1'
|
||||||
|
s.summary = 'iOS and macOS implementation of the shared_preferences plugin.'
|
||||||
|
s.description = <<-DESC
|
||||||
|
Wraps NSUserDefaults, providing a persistent store for simple key-value pairs.
|
||||||
|
DESC
|
||||||
|
s.homepage = 'https://github.com/flutter/plugins/tree/main/packages/shared_preferences/shared_preferences_foundation'
|
||||||
|
s.license = { :type => 'BSD', :file => '../LICENSE' }
|
||||||
|
s.author = { 'Flutter Team' => 'flutter-dev@googlegroups.com' }
|
||||||
|
s.source = { :http => 'https://github.com/flutter/plugins/tree/main/packages/shared_preferences/shared_preferences_foundation' }
|
||||||
|
s.source_files = 'Classes/**/*'
|
||||||
|
s.ios.dependency 'Flutter'
|
||||||
|
s.osx.dependency 'FlutterMacOS'
|
||||||
|
s.ios.deployment_target = '9.0'
|
||||||
|
s.osx.deployment_target = '10.11'
|
||||||
|
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' }
|
||||||
|
$swiftWorkaround
|
||||||
|
s.swift_version = '5.0'
|
||||||
|
|
||||||
|
end
|
||||||
|
''');
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('PodspecCheckCommand', () {
|
||||||
|
FileSystem fileSystem;
|
||||||
|
late Directory packagesDir;
|
||||||
|
late CommandRunner<void> runner;
|
||||||
|
late MockPlatform mockPlatform;
|
||||||
|
late RecordingProcessRunner processRunner;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
fileSystem = MemoryFileSystem();
|
||||||
|
packagesDir = createPackagesDirectory(fileSystem: fileSystem);
|
||||||
|
|
||||||
|
mockPlatform = MockPlatform(isMacOS: true);
|
||||||
|
processRunner = RecordingProcessRunner();
|
||||||
|
final PodspecCheckCommand command = PodspecCheckCommand(
|
||||||
|
packagesDir,
|
||||||
|
processRunner: processRunner,
|
||||||
|
platform: mockPlatform,
|
||||||
|
);
|
||||||
|
|
||||||
|
runner =
|
||||||
|
CommandRunner<void>('podspec_test', 'Test for $PodspecCheckCommand');
|
||||||
|
runner.addCommand(command);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('only runs on macOS', () async {
|
||||||
|
createFakePlugin('plugin1', packagesDir,
|
||||||
|
extraFiles: <String>['plugin1.podspec']);
|
||||||
|
mockPlatform.isMacOS = false;
|
||||||
|
|
||||||
|
Error? commandError;
|
||||||
|
final List<String> output = await runCapturingPrint(
|
||||||
|
runner, <String>['podspec-check'], errorHandler: (Error e) {
|
||||||
|
commandError = e;
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(commandError, isA<ToolExit>());
|
||||||
|
|
||||||
|
expect(
|
||||||
|
processRunner.recordedCalls,
|
||||||
|
equals(<ProcessCall>[]),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
output,
|
||||||
|
containsAllInOrder(
|
||||||
|
<Matcher>[contains('only supported on macOS')],
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('runs pod lib lint on a podspec', () async {
|
||||||
|
final RepositoryPackage plugin = createFakePlugin(
|
||||||
|
'plugin1',
|
||||||
|
packagesDir,
|
||||||
|
extraFiles: <String>[
|
||||||
|
'bogus.dart', // Ignore non-podspecs.
|
||||||
|
],
|
||||||
|
);
|
||||||
|
_writeFakePodspec(plugin, 'ios');
|
||||||
|
|
||||||
|
processRunner.mockProcessesForExecutable['pod'] = <io.Process>[
|
||||||
|
MockProcess(stdout: 'Foo', stderr: 'Bar'),
|
||||||
|
MockProcess(),
|
||||||
|
];
|
||||||
|
|
||||||
|
final List<String> output =
|
||||||
|
await runCapturingPrint(runner, <String>['podspec-check']);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
processRunner.recordedCalls,
|
||||||
|
orderedEquals(<ProcessCall>[
|
||||||
|
ProcessCall('which', const <String>['pod'], packagesDir.path),
|
||||||
|
ProcessCall(
|
||||||
|
'pod',
|
||||||
|
<String>[
|
||||||
|
'lib',
|
||||||
|
'lint',
|
||||||
|
plugin
|
||||||
|
.platformDirectory(FlutterPlatform.ios)
|
||||||
|
.childFile('plugin1.podspec')
|
||||||
|
.path,
|
||||||
|
'--configuration=Debug',
|
||||||
|
'--skip-tests',
|
||||||
|
'--use-modular-headers',
|
||||||
|
'--use-libraries'
|
||||||
|
],
|
||||||
|
packagesDir.path),
|
||||||
|
ProcessCall(
|
||||||
|
'pod',
|
||||||
|
<String>[
|
||||||
|
'lib',
|
||||||
|
'lint',
|
||||||
|
plugin
|
||||||
|
.platformDirectory(FlutterPlatform.ios)
|
||||||
|
.childFile('plugin1.podspec')
|
||||||
|
.path,
|
||||||
|
'--configuration=Debug',
|
||||||
|
'--skip-tests',
|
||||||
|
'--use-modular-headers',
|
||||||
|
],
|
||||||
|
packagesDir.path),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(output, contains('Linting plugin1.podspec'));
|
||||||
|
expect(output, contains('Foo'));
|
||||||
|
expect(output, contains('Bar'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('fails if pod is missing', () async {
|
||||||
|
final RepositoryPackage plugin = createFakePlugin('plugin1', packagesDir);
|
||||||
|
_writeFakePodspec(plugin, 'ios');
|
||||||
|
|
||||||
|
// Simulate failure from `which pod`.
|
||||||
|
processRunner.mockProcessesForExecutable['which'] = <io.Process>[
|
||||||
|
MockProcess(exitCode: 1),
|
||||||
|
];
|
||||||
|
|
||||||
|
Error? commandError;
|
||||||
|
final List<String> output = await runCapturingPrint(
|
||||||
|
runner, <String>['podspec-check'], errorHandler: (Error e) {
|
||||||
|
commandError = e;
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(commandError, isA<ToolExit>());
|
||||||
|
|
||||||
|
expect(
|
||||||
|
output,
|
||||||
|
containsAllInOrder(
|
||||||
|
<Matcher>[
|
||||||
|
contains('Unable to find "pod". Make sure it is in your path.'),
|
||||||
|
],
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('fails if linting as a framework fails', () async {
|
||||||
|
final RepositoryPackage plugin = createFakePlugin('plugin1', packagesDir);
|
||||||
|
_writeFakePodspec(plugin, 'ios');
|
||||||
|
|
||||||
|
// Simulate failure from `pod`.
|
||||||
|
processRunner.mockProcessesForExecutable['pod'] = <io.Process>[
|
||||||
|
MockProcess(exitCode: 1),
|
||||||
|
];
|
||||||
|
|
||||||
|
Error? commandError;
|
||||||
|
final List<String> output = await runCapturingPrint(
|
||||||
|
runner, <String>['podspec-check'], errorHandler: (Error e) {
|
||||||
|
commandError = e;
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(commandError, isA<ToolExit>());
|
||||||
|
|
||||||
|
expect(
|
||||||
|
output,
|
||||||
|
containsAllInOrder(
|
||||||
|
<Matcher>[
|
||||||
|
contains('The following packages had errors:'),
|
||||||
|
contains('plugin1:\n'
|
||||||
|
' plugin1.podspec')
|
||||||
|
],
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('fails if linting as a static library fails', () async {
|
||||||
|
final RepositoryPackage plugin = createFakePlugin('plugin1', packagesDir);
|
||||||
|
_writeFakePodspec(plugin, 'ios');
|
||||||
|
|
||||||
|
// Simulate failure from the second call to `pod`.
|
||||||
|
processRunner.mockProcessesForExecutable['pod'] = <io.Process>[
|
||||||
|
MockProcess(),
|
||||||
|
MockProcess(exitCode: 1),
|
||||||
|
];
|
||||||
|
|
||||||
|
Error? commandError;
|
||||||
|
final List<String> output = await runCapturingPrint(
|
||||||
|
runner, <String>['podspec-check'], errorHandler: (Error e) {
|
||||||
|
commandError = e;
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(commandError, isA<ToolExit>());
|
||||||
|
|
||||||
|
expect(
|
||||||
|
output,
|
||||||
|
containsAllInOrder(
|
||||||
|
<Matcher>[
|
||||||
|
contains('The following packages had errors:'),
|
||||||
|
contains('plugin1:\n'
|
||||||
|
' plugin1.podspec')
|
||||||
|
],
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('fails if an iOS Swift plugin is missing the search paths workaround',
|
||||||
|
() async {
|
||||||
|
final RepositoryPackage plugin = createFakePlugin('plugin1', packagesDir,
|
||||||
|
extraFiles: <String>['ios/Classes/SomeSwift.swift']);
|
||||||
|
_writeFakePodspec(plugin, 'ios');
|
||||||
|
|
||||||
|
Error? commandError;
|
||||||
|
final List<String> output = await runCapturingPrint(
|
||||||
|
runner, <String>['podspec-check'], errorHandler: (Error e) {
|
||||||
|
commandError = e;
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(commandError, isA<ToolExit>());
|
||||||
|
|
||||||
|
expect(
|
||||||
|
output,
|
||||||
|
containsAllInOrder(
|
||||||
|
<Matcher>[
|
||||||
|
contains(r'''
|
||||||
|
s.xcconfig = {
|
||||||
|
'LIBRARY_SEARCH_PATHS' => '$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)/ $(SDKROOT)/usr/lib/swift',
|
||||||
|
'LD_RUNPATH_SEARCH_PATHS' => '/usr/lib/swift',
|
||||||
|
}'''),
|
||||||
|
contains('The following packages had errors:'),
|
||||||
|
contains('plugin1:\n'
|
||||||
|
' plugin1.podspec')
|
||||||
|
],
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
'fails if a shared-source Swift plugin is missing the search paths workaround',
|
||||||
|
() async {
|
||||||
|
final RepositoryPackage plugin = createFakePlugin('plugin1', packagesDir,
|
||||||
|
extraFiles: <String>['darwin/Classes/SomeSwift.swift']);
|
||||||
|
_writeFakePodspec(plugin, 'darwin');
|
||||||
|
|
||||||
|
Error? commandError;
|
||||||
|
final List<String> output = await runCapturingPrint(
|
||||||
|
runner, <String>['podspec-check'], errorHandler: (Error e) {
|
||||||
|
commandError = e;
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(commandError, isA<ToolExit>());
|
||||||
|
|
||||||
|
expect(
|
||||||
|
output,
|
||||||
|
containsAllInOrder(
|
||||||
|
<Matcher>[
|
||||||
|
contains(r'''
|
||||||
|
s.xcconfig = {
|
||||||
|
'LIBRARY_SEARCH_PATHS' => '$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)/ $(SDKROOT)/usr/lib/swift',
|
||||||
|
'LD_RUNPATH_SEARCH_PATHS' => '/usr/lib/swift',
|
||||||
|
}'''),
|
||||||
|
contains('The following packages had errors:'),
|
||||||
|
contains('plugin1:\n'
|
||||||
|
' plugin1.podspec')
|
||||||
|
],
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('does not require the search paths workaround for macOS plugins',
|
||||||
|
() async {
|
||||||
|
final RepositoryPackage plugin = createFakePlugin('plugin1', packagesDir,
|
||||||
|
extraFiles: <String>['macos/Classes/SomeSwift.swift']);
|
||||||
|
_writeFakePodspec(plugin, 'macos');
|
||||||
|
|
||||||
|
final List<String> output =
|
||||||
|
await runCapturingPrint(runner, <String>['podspec-check']);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
output,
|
||||||
|
containsAllInOrder(
|
||||||
|
<Matcher>[
|
||||||
|
contains('Ran for 1 package(s)'),
|
||||||
|
],
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('does not require the search paths workaround for ObjC iOS plugins',
|
||||||
|
() async {
|
||||||
|
final RepositoryPackage plugin = createFakePlugin('plugin1', packagesDir,
|
||||||
|
extraFiles: <String>[
|
||||||
|
'ios/Classes/SomeObjC.h',
|
||||||
|
'ios/Classes/SomeObjC.m'
|
||||||
|
]);
|
||||||
|
_writeFakePodspec(plugin, 'ios');
|
||||||
|
|
||||||
|
final List<String> output =
|
||||||
|
await runCapturingPrint(runner, <String>['podspec-check']);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
output,
|
||||||
|
containsAllInOrder(
|
||||||
|
<Matcher>[
|
||||||
|
contains('Ran for 1 package(s)'),
|
||||||
|
],
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('passes if the search paths workaround is present', () async {
|
||||||
|
final RepositoryPackage plugin = createFakePlugin('plugin1', packagesDir,
|
||||||
|
extraFiles: <String>['ios/Classes/SomeSwift.swift']);
|
||||||
|
_writeFakePodspec(plugin, 'ios', includeSwiftWorkaround: true);
|
||||||
|
|
||||||
|
final List<String> output =
|
||||||
|
await runCapturingPrint(runner, <String>['podspec-check']);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
output,
|
||||||
|
containsAllInOrder(
|
||||||
|
<Matcher>[
|
||||||
|
contains('Ran for 1 package(s)'),
|
||||||
|
],
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('passes if the search paths workaround is present for iOS only',
|
||||||
|
() async {
|
||||||
|
final RepositoryPackage plugin = createFakePlugin('plugin1', packagesDir,
|
||||||
|
extraFiles: <String>['ios/Classes/SomeSwift.swift']);
|
||||||
|
_writeFakePodspec(plugin, 'ios',
|
||||||
|
includeSwiftWorkaround: true, scopeSwiftWorkaround: true);
|
||||||
|
|
||||||
|
final List<String> output =
|
||||||
|
await runCapturingPrint(runner, <String>['podspec-check']);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
output,
|
||||||
|
containsAllInOrder(
|
||||||
|
<Matcher>[
|
||||||
|
contains('Ran for 1 package(s)'),
|
||||||
|
],
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('does not require the search paths workaround for Swift example code',
|
||||||
|
() async {
|
||||||
|
final RepositoryPackage plugin =
|
||||||
|
createFakePlugin('plugin1', packagesDir, extraFiles: <String>[
|
||||||
|
'ios/Classes/SomeObjC.h',
|
||||||
|
'ios/Classes/SomeObjC.m',
|
||||||
|
'example/ios/Runner/AppDelegate.swift',
|
||||||
|
]);
|
||||||
|
_writeFakePodspec(plugin, 'ios');
|
||||||
|
|
||||||
|
final List<String> output =
|
||||||
|
await runCapturingPrint(runner, <String>['podspec-check']);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
output,
|
||||||
|
containsAllInOrder(
|
||||||
|
<Matcher>[
|
||||||
|
contains('Ran for 1 package(s)'),
|
||||||
|
],
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('skips when there are no podspecs', () async {
|
||||||
|
createFakePlugin('plugin1', packagesDir);
|
||||||
|
|
||||||
|
final List<String> output =
|
||||||
|
await runCapturingPrint(runner, <String>['podspec-check']);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
output,
|
||||||
|
containsAllInOrder(
|
||||||
|
<Matcher>[contains('SKIPPING: No podspecs.')],
|
||||||
|
));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
Reference in New Issue
Block a user