diff --git a/.ci.yaml b/.ci.yaml index 96868d5a73e..66f95e165ae 100644 --- a/.ci.yaml +++ b/.ci.yaml @@ -1071,7 +1071,11 @@ targets: properties: add_recipes_cq: "true" version_file: flutter_master.version - target_file: macos_check_podspecs.yaml + target_file: macos_repo_checks.yaml + dependencies: > + [ + {"dependency": "swift_format", "version": "build_id:8797338980206841409"} + ] ### macOS desktop tasks ### # macos-platform_tests builds all the packages on ARM, so this build is run @@ -1143,6 +1147,10 @@ targets: { "CHANNEL": "master" } + dependencies: > + [ + {"dependency": "swift_format", "version": "build_id:8797338979890974865"} + ] - name: Mac_arm64 custom_package_tests stable recipe: packages/packages @@ -1156,6 +1164,10 @@ targets: { "CHANNEL": "stable" } + dependencies: > + [ + {"dependency": "swift_format", "version": "build_id:8797338979890974865"} + ] ### iOS tasks ### # ios_platform_tests builds all the packages on ARM, so this build is run diff --git a/.ci/targets/macos_check_podspecs.yaml b/.ci/targets/macos_repo_checks.yaml similarity index 56% rename from .ci/targets/macos_check_podspecs.yaml rename to .ci/targets/macos_repo_checks.yaml index 946ac66f748..7fd858bfe83 100644 --- a/.ci/targets/macos_check_podspecs.yaml +++ b/.ci/targets/macos_repo_checks.yaml @@ -5,6 +5,13 @@ tasks: - name: update pods repo script: .ci/scripts/update_pods.sh infra_step: true # Note infra steps failing prevents "always" from running. + - name: Swift format + script: script/tool_runner.sh + # Non-Swift languages are formatted on Linux builders. + # Skip them on Mac builder to avoid duplication. + args: ["format", "--fail-on-change", "--no-dart", "--no-clang-format", "--no-kotlin", "--no-java" ] + always: true - name: validate iOS and macOS podspecs script: script/tool_runner.sh args: ["podspec-check"] + always: true diff --git a/.ci/targets/repo_checks.yaml b/.ci/targets/repo_checks.yaml index 5989a5efed7..37d9f0a1008 100644 --- a/.ci/targets/repo_checks.yaml +++ b/.ci/targets/repo_checks.yaml @@ -4,7 +4,8 @@ tasks: infra_step: true # Note infra steps failing prevents "always" from running. - name: format script: script/tool_runner.sh - args: ["format", "--fail-on-change"] + # Skip Swift formatting on Linux builders. + args: ["format", "--fail-on-change", "--no-swift"] always: true - name: license validation script: script/tool_runner.sh diff --git a/packages/file_selector/file_selector_macos/example/macos/RunnerTests/RunnerTests.swift b/packages/file_selector/file_selector_macos/example/macos/RunnerTests/RunnerTests.swift index 53081ed6548..304646a9b59 100644 --- a/packages/file_selector/file_selector_macos/example/macos/RunnerTests/RunnerTests.swift +++ b/packages/file_selector/file_selector_macos/example/macos/RunnerTests/RunnerTests.swift @@ -40,7 +40,7 @@ class TestViewProvider: NSObject, ViewProvider { var window: NSWindow? = NSWindow() } -class exampleTests: XCTestCase { +class ExampleTests: XCTestCase { func testOpenSimple() throws { let panelController = TestPanelController() diff --git a/packages/pigeon/tool/shared/generation.dart b/packages/pigeon/tool/shared/generation.dart index 1ae5a45edc3..03952ddbcfd 100644 --- a/packages/pigeon/tool/shared/generation.dart +++ b/packages/pigeon/tool/shared/generation.dart @@ -242,6 +242,7 @@ Future formatAllFiles({required String repositoryRoot}) { 'run', 'script/tool/bin/flutter_plugin_tools.dart', 'format', + '--no-swift', '--packages=pigeon', ], workingDirectory: repositoryRoot, diff --git a/script/tool/lib/src/format_command.dart b/script/tool/lib/src/format_command.dart index aa19bc96af1..c0030949086 100644 --- a/script/tool/lib/src/format_command.dart +++ b/script/tool/lib/src/format_command.dart @@ -54,7 +54,8 @@ class FormatCommand extends PackageCommand { argParser.addFlag(_kotlinArg, help: 'Format Kotlin files', defaultsTo: true); argParser.addFlag(_javaArg, help: 'Format Java files', defaultsTo: true); - argParser.addFlag(_swiftArg, help: 'Format Swift files'); + argParser.addFlag(_swiftArg, + help: 'Format and lint Swift files', defaultsTo: true); argParser.addOption(_clangFormatPathArg, defaultsTo: 'clang-format', help: 'Path to "clang-format" executable.'); argParser.addOption(_javaPathArg, @@ -105,7 +106,7 @@ class FormatCommand extends PackageCommand { await _formatCppAndObjectiveC(files); } if (getBoolArg(_swiftArg)) { - await _formatSwift(files); + await _formatAndLintSwift(files); } if (getBoolArg('fail-on-change')) { @@ -177,16 +178,32 @@ class FormatCommand extends PackageCommand { } } - Future _formatSwift(Iterable files) async { - final String swiftFormat = await _findValidSwiftFormat(); - final Iterable swiftFiles = - _getPathsWithExtensions(files, {'.swift'}); + Future _formatAndLintSwift(Iterable files) async { + // TODO(jmagman): Remove generated file filter when pigeon Swift generation matches swift-format. + // https://github.com/flutter/flutter/issues/141799 + final Iterable swiftFiles = _filterGeneratedFiles( + _getPathsWithExtensions(files, {'.swift'})); if (swiftFiles.isNotEmpty) { + final String swiftFormat = await _findValidSwiftFormat(); print('Formatting .swift files...'); - final int exitCode = + final int formatExitCode = await _runBatched(swiftFormat, ['-i'], files: swiftFiles); - if (exitCode != 0) { - printError('Failed to format Swift files: exit code $exitCode.'); + if (formatExitCode != 0) { + printError('Failed to format Swift files: exit code $formatExitCode.'); + throw ToolExit(_exitSwiftFormatFailed); + } + + print('Linting .swift files...'); + final int lintExitCode = await _runBatched( + swiftFormat, + [ + 'lint', + '--parallel', + '--strict', + ], + files: swiftFiles); + if (lintExitCode != 0) { + printError('Failed to lint Swift files: exit code $lintExitCode.'); throw ToolExit(_exitSwiftFormatFailed); } } @@ -342,6 +359,13 @@ class FormatCommand extends PackageCommand { (String filePath) => extensions.contains(path.extension(filePath))); } + Iterable _filterGeneratedFiles(Iterable files) { + return files.where((String filePath) { + final String basename = path.basename(filePath); + return !basename.contains('.gen.') && !basename.contains('.g.'); + }); + } + Future _getJavaFormatterPath() async { final String javaFormatterPath = path.join( path.dirname(path.fromUri(platform.script)), diff --git a/script/tool/test/format_command_test.dart b/script/tool/test/format_command_test.dart index 73d1864322a..846466e2aad 100644 --- a/script/tool/test/format_command_test.dart +++ b/script/tool/test/format_command_test.dart @@ -551,15 +551,45 @@ void main() { processRunner.recordedCalls, orderedEquals([ const ProcessCall( - '/path/to/swift-format', ['--version'], null), + '/path/to/swift-format', + ['--version'], + null, + ), ProcessCall( - '/path/to/swift-format', - ['-i', ...getPackagesDirRelativePaths(plugin, files)], - packagesDir.path), + '/path/to/swift-format', + ['-i', ...getPackagesDirRelativePaths(plugin, files)], + packagesDir.path, + ), + ProcessCall( + '/path/to/swift-format', + [ + 'lint', + '--parallel', + '--strict', + ...getPackagesDirRelativePaths(plugin, files), + ], + packagesDir.path, + ), ])); }); - test('skips Swift if --swift flag is not provided', () async { + test('skips generated Swift files', () async { + const List files = [ + 'macos/foo.gen.swift', + 'macos/foo.g.swift', + ]; + createFakePlugin( + 'a_plugin', + packagesDir, + extraFiles: files, + ); + + await runCapturingPrint(runner, ['format', '--swift']); + + expect(processRunner.recordedCalls, orderedEquals([])); + }); + + test('skips Swift if --no-swift flag is provided', () async { const List files = [ 'macos/foo.swift', ]; @@ -569,7 +599,7 @@ void main() { extraFiles: files, ); - await runCapturingPrint(runner, ['format']); + await runCapturingPrint(runner, ['format', '--no-swift']); expect(processRunner.recordedCalls, orderedEquals([])); }); @@ -601,6 +631,40 @@ void main() { ])); }); + test('fails if swift-format lint fails', () async { + const List files = [ + 'macos/foo.swift', + ]; + createFakePlugin('a_plugin', packagesDir, extraFiles: files); + + processRunner.mockProcessesForExecutable['swift-format'] = + [ + FakeProcessInfo(MockProcess(), + ['--version']), // check for working swift-format + FakeProcessInfo(MockProcess(), ['-i']), + FakeProcessInfo(MockProcess(exitCode: 1), [ + 'lint', + '--parallel', + '--strict', + ]), + ]; + Error? commandError; + final List output = await runCapturingPrint(runner, [ + 'format', + '--swift', + '--swift-format-path=swift-format' + ], errorHandler: (Error e) { + commandError = e; + }); + + expect(commandError, isA()); + expect( + output, + containsAllInOrder([ + contains('Failed to lint Swift files: exit code 1.'), + ])); + }); + test('fails if swift-format fails', () async { const List files = [ 'macos/foo.swift',