diff --git a/.ci.yaml b/.ci.yaml index c77aa42c2af1..f88c794712ba 100644 --- a/.ci.yaml +++ b/.ci.yaml @@ -43,6 +43,7 @@ targets: # TODO(stuartmorgan): Move this to ARM once google_maps_flutter has ARM # support. `pod lint` makes a synthetic target that doesn't respect the # pod's arch exclusions, so fails to build. + # When moving it, rename the task and file to check_podspecs - name: Mac_x64 lint_podspecs recipe: plugins/plugins timeout: 30 diff --git a/.ci/targets/mac_lint_podspecs.yaml b/.ci/targets/mac_lint_podspecs.yaml index 02a904ee3d85..0b2217325635 100644 --- a/.ci/targets/mac_lint_podspecs.yaml +++ b/.ci/targets/mac_lint_podspecs.yaml @@ -1,6 +1,6 @@ tasks: - name: prepare tool script: .ci/scripts/prepare_tool.sh - - name: lint iOS and macOS podspecs + - name: validate iOS and macOS podspecs script: script/tool_runner.sh - args: ["podspecs"] + args: ["podspec-check"] diff --git a/script/tool/CHANGELOG.md b/script/tool/CHANGELOG.md index ffb56bd7a7a4..55b5aeb7222a 100644 --- a/script/tool/CHANGELOG.md +++ b/script/tool/CHANGELOG.md @@ -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 * Replaces deprecated `flutter format` with `dart format` in `format` diff --git a/script/tool/lib/src/main.dart b/script/tool/lib/src/main.dart index d5ab7f88089e..0083e0cbb8ee 100644 --- a/script/tool/lib/src/main.dart +++ b/script/tool/lib/src/main.dart @@ -21,10 +21,10 @@ import 'fix_command.dart'; import 'format_command.dart'; import 'license_check_command.dart'; import 'lint_android_command.dart'; -import 'lint_podspecs_command.dart'; import 'list_command.dart'; import 'make_deps_path_based_command.dart'; import 'native_test_command.dart'; +import 'podspec_check_command.dart'; import 'publish_check_command.dart'; import 'publish_command.dart'; import 'pubspec_check_command.dart'; @@ -66,7 +66,7 @@ void main(List args) { ..addCommand(FormatCommand(packagesDir)) ..addCommand(LicenseCheckCommand(packagesDir)) ..addCommand(LintAndroidCommand(packagesDir)) - ..addCommand(LintPodspecsCommand(packagesDir)) + ..addCommand(PodspecCheckCommand(packagesDir)) ..addCommand(ListCommand(packagesDir)) ..addCommand(NativeTestCommand(packagesDir)) ..addCommand(MakeDepsPathBasedCommand(packagesDir)) diff --git a/script/tool/lib/src/lint_podspecs_command.dart b/script/tool/lib/src/podspec_check_command.dart similarity index 54% rename from script/tool/lib/src/lint_podspecs_command.dart rename to script/tool/lib/src/podspec_check_command.dart index 198dd9472115..4cda7210a8ef 100644 --- a/script/tool/lib/src/lint_podspecs_command.dart +++ b/script/tool/lib/src/podspec_check_command.dart @@ -6,7 +6,6 @@ import 'dart:convert'; import 'dart:io'; import 'package:file/file.dart'; -import 'package:path/path.dart' as p; import 'package:platform/platform.dart'; import 'common/core.dart'; @@ -20,23 +19,24 @@ const int _exitPodNotInstalled = 3; /// Lint the CocoaPod podspecs and run unit tests. /// /// 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. - LintPodspecsCommand( + PodspecCheckCommand( Directory packagesDir, { ProcessRunner processRunner = const ProcessRunner(), Platform platform = const LocalPlatform(), }) : super(packagesDir, processRunner: processRunner, platform: platform); @override - final String name = 'podspecs'; + final String name = 'podspec-check'; @override - List get aliases => ['podspec']; + List get aliases => ['podspec', 'podspecs']; @override 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.'; @override @@ -69,9 +69,32 @@ class LintPodspecsCommand extends PackageLoopingCommand { for (final File podspec in podspecs) { 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 ? PackageResult.success() : PackageResult.fail(errors); @@ -92,7 +115,7 @@ class LintPodspecsCommand extends PackageLoopingCommand { // Do not run the static analyzer on plugins with known analyzer issues. final String podspecPath = podspec.path; - final String podspecBasename = p.basename(podspecPath); + final String podspecBasename = podspec.basename; print('Linting $podspecBasename'); // Lint plugin as framework (use_frameworks!). @@ -126,4 +149,46 @@ class LintPodspecsCommand extends PackageLoopingCommand { return processRunner.run('pod', arguments, workingDir: packagesDir, stdoutEncoding: utf8, stderrEncoding: utf8); } + + /// Returns true if there is any iOS plugin implementation code written in + /// Swift. + Future _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()); + } } diff --git a/script/tool/pubspec.yaml b/script/tool/pubspec.yaml index 5438726d5bca..abf2a61f4cf0 100644 --- a/script/tool/pubspec.yaml +++ b/script/tool/pubspec.yaml @@ -1,7 +1,7 @@ name: flutter_plugin_tools description: Productivity utils for flutter/plugins and flutter/packages repository: https://github.com/flutter/plugins/tree/main/script/tool -version: 0.13.2+1 +version: 0.13.3 dependencies: args: ^2.1.0 diff --git a/script/tool/test/lint_podspecs_command_test.dart b/script/tool/test/lint_podspecs_command_test.dart deleted file mode 100644 index 097bcff338a5..000000000000 --- a/script/tool/test/lint_podspecs_command_test.dart +++ /dev/null @@ -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 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('podspec_test', 'Test for $LintPodspecsCommand'); - runner.addCommand(command); - }); - - test('only runs on macOS', () async { - createFakePlugin('plugin1', packagesDir, - extraFiles: ['plugin1.podspec']); - mockPlatform.isMacOS = false; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['podspecs'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - - expect( - processRunner.recordedCalls, - equals([]), - ); - - expect( - output, - containsAllInOrder( - [contains('only supported on macOS')], - )); - }); - - test('runs pod lib lint on a podspec', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin1', - packagesDir, - extraFiles: [ - 'ios/plugin1.podspec', - 'bogus.dart', // Ignore non-podspecs. - ], - ); - - processRunner.mockProcessesForExecutable['pod'] = [ - MockProcess(stdout: 'Foo', stderr: 'Bar'), - MockProcess(), - ]; - - final List output = - await runCapturingPrint(runner, ['podspecs']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall('which', const ['pod'], packagesDir.path), - ProcessCall( - 'pod', - [ - 'lib', - 'lint', - plugin - .platformDirectory(FlutterPlatform.ios) - .childFile('plugin1.podspec') - .path, - '--configuration=Debug', - '--skip-tests', - '--use-modular-headers', - '--use-libraries' - ], - packagesDir.path), - ProcessCall( - 'pod', - [ - '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: ['plugin1.podspec']); - - // Simulate failure from `which pod`. - processRunner.mockProcessesForExecutable['which'] = [ - MockProcess(exitCode: 1), - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['podspecs'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - - expect( - output, - containsAllInOrder( - [ - 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: ['plugin1.podspec']); - - // Simulate failure from `pod`. - processRunner.mockProcessesForExecutable['pod'] = [ - MockProcess(exitCode: 1), - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['podspecs'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - - expect( - output, - containsAllInOrder( - [ - 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: ['plugin1.podspec']); - - // Simulate failure from the second call to `pod`. - processRunner.mockProcessesForExecutable['pod'] = [ - MockProcess(), - MockProcess(exitCode: 1), - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['podspecs'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - - expect( - output, - containsAllInOrder( - [ - contains('The following packages had errors:'), - contains('plugin1:\n' - ' plugin1.podspec') - ], - )); - }); - - test('skips when there are no podspecs', () async { - createFakePlugin('plugin1', packagesDir); - - final List output = - await runCapturingPrint(runner, ['podspecs']); - - expect( - output, - containsAllInOrder( - [contains('SKIPPING: No podspecs.')], - )); - }); - }); -} diff --git a/script/tool/test/podspec_check_command_test.dart b/script/tool/test/podspec_check_command_test.dart new file mode 100644 index 000000000000..c31ffd46a4b7 --- /dev/null +++ b/script/tool/test/podspec_check_command_test.dart @@ -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 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('podspec_test', 'Test for $PodspecCheckCommand'); + runner.addCommand(command); + }); + + test('only runs on macOS', () async { + createFakePlugin('plugin1', packagesDir, + extraFiles: ['plugin1.podspec']); + mockPlatform.isMacOS = false; + + Error? commandError; + final List output = await runCapturingPrint( + runner, ['podspec-check'], errorHandler: (Error e) { + commandError = e; + }); + + expect(commandError, isA()); + + expect( + processRunner.recordedCalls, + equals([]), + ); + + expect( + output, + containsAllInOrder( + [contains('only supported on macOS')], + )); + }); + + test('runs pod lib lint on a podspec', () async { + final RepositoryPackage plugin = createFakePlugin( + 'plugin1', + packagesDir, + extraFiles: [ + 'bogus.dart', // Ignore non-podspecs. + ], + ); + _writeFakePodspec(plugin, 'ios'); + + processRunner.mockProcessesForExecutable['pod'] = [ + MockProcess(stdout: 'Foo', stderr: 'Bar'), + MockProcess(), + ]; + + final List output = + await runCapturingPrint(runner, ['podspec-check']); + + expect( + processRunner.recordedCalls, + orderedEquals([ + ProcessCall('which', const ['pod'], packagesDir.path), + ProcessCall( + 'pod', + [ + 'lib', + 'lint', + plugin + .platformDirectory(FlutterPlatform.ios) + .childFile('plugin1.podspec') + .path, + '--configuration=Debug', + '--skip-tests', + '--use-modular-headers', + '--use-libraries' + ], + packagesDir.path), + ProcessCall( + 'pod', + [ + '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'] = [ + MockProcess(exitCode: 1), + ]; + + Error? commandError; + final List output = await runCapturingPrint( + runner, ['podspec-check'], errorHandler: (Error e) { + commandError = e; + }); + + expect(commandError, isA()); + + expect( + output, + containsAllInOrder( + [ + 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'] = [ + MockProcess(exitCode: 1), + ]; + + Error? commandError; + final List output = await runCapturingPrint( + runner, ['podspec-check'], errorHandler: (Error e) { + commandError = e; + }); + + expect(commandError, isA()); + + expect( + output, + containsAllInOrder( + [ + 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'] = [ + MockProcess(), + MockProcess(exitCode: 1), + ]; + + Error? commandError; + final List output = await runCapturingPrint( + runner, ['podspec-check'], errorHandler: (Error e) { + commandError = e; + }); + + expect(commandError, isA()); + + expect( + output, + containsAllInOrder( + [ + 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: ['ios/Classes/SomeSwift.swift']); + _writeFakePodspec(plugin, 'ios'); + + Error? commandError; + final List output = await runCapturingPrint( + runner, ['podspec-check'], errorHandler: (Error e) { + commandError = e; + }); + + expect(commandError, isA()); + + expect( + output, + containsAllInOrder( + [ + 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: ['darwin/Classes/SomeSwift.swift']); + _writeFakePodspec(plugin, 'darwin'); + + Error? commandError; + final List output = await runCapturingPrint( + runner, ['podspec-check'], errorHandler: (Error e) { + commandError = e; + }); + + expect(commandError, isA()); + + expect( + output, + containsAllInOrder( + [ + 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: ['macos/Classes/SomeSwift.swift']); + _writeFakePodspec(plugin, 'macos'); + + final List output = + await runCapturingPrint(runner, ['podspec-check']); + + expect( + output, + containsAllInOrder( + [ + 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: [ + 'ios/Classes/SomeObjC.h', + 'ios/Classes/SomeObjC.m' + ]); + _writeFakePodspec(plugin, 'ios'); + + final List output = + await runCapturingPrint(runner, ['podspec-check']); + + expect( + output, + containsAllInOrder( + [ + contains('Ran for 1 package(s)'), + ], + )); + }); + + test('passes if the search paths workaround is present', () async { + final RepositoryPackage plugin = createFakePlugin('plugin1', packagesDir, + extraFiles: ['ios/Classes/SomeSwift.swift']); + _writeFakePodspec(plugin, 'ios', includeSwiftWorkaround: true); + + final List output = + await runCapturingPrint(runner, ['podspec-check']); + + expect( + output, + containsAllInOrder( + [ + 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: ['ios/Classes/SomeSwift.swift']); + _writeFakePodspec(plugin, 'ios', + includeSwiftWorkaround: true, scopeSwiftWorkaround: true); + + final List output = + await runCapturingPrint(runner, ['podspec-check']); + + expect( + output, + containsAllInOrder( + [ + 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: [ + 'ios/Classes/SomeObjC.h', + 'ios/Classes/SomeObjC.m', + 'example/ios/Runner/AppDelegate.swift', + ]); + _writeFakePodspec(plugin, 'ios'); + + final List output = + await runCapturingPrint(runner, ['podspec-check']); + + expect( + output, + containsAllInOrder( + [ + contains('Ran for 1 package(s)'), + ], + )); + }); + + test('skips when there are no podspecs', () async { + createFakePlugin('plugin1', packagesDir); + + final List output = + await runCapturingPrint(runner, ['podspec-check']); + + expect( + output, + containsAllInOrder( + [contains('SKIPPING: No podspecs.')], + )); + }); + }); +}