mirror of
https://github.com/flutter/packages.git
synced 2025-06-05 19:17:51 +08:00
[pigeon] Support other hosts in generated file CI checks (#5944)
Reworks Pigeon's CI validation of generated files to support multiple hosts, rather than only Linux, with the ability to set specific languages per host, to allow us to run validation for each language on whatever host it is most convenient to set up a formatter for that language, rather than having to support all languages on Linux. See discussion in https://github.com/flutter/packages/pull/5928 Part of https://github.com/flutter/flutter/issues/41129
This commit is contained in:
@ -34,7 +34,9 @@ private func wrapError(_ error: Any) -> [Any?] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func createConnectionError(withChannelName channelName: String) -> FlutterError {
|
private func createConnectionError(withChannelName channelName: String) -> FlutterError {
|
||||||
return FlutterError(code: "channel-error", message: "Unable to establish connection on channel: '\(channelName)'.", details: "")
|
return FlutterError(
|
||||||
|
code: "channel-error", message: "Unable to establish connection on channel: '\(channelName)'.",
|
||||||
|
details: "")
|
||||||
}
|
}
|
||||||
|
|
||||||
private func isNullish(_ value: Any?) -> Bool {
|
private func isNullish(_ value: Any?) -> Bool {
|
||||||
@ -130,7 +132,9 @@ class ExampleHostApiSetup {
|
|||||||
static var codec: FlutterStandardMessageCodec { ExampleHostApiCodec.shared }
|
static var codec: FlutterStandardMessageCodec { ExampleHostApiCodec.shared }
|
||||||
/// Sets up an instance of `ExampleHostApi` to handle messages through the `binaryMessenger`.
|
/// Sets up an instance of `ExampleHostApi` to handle messages through the `binaryMessenger`.
|
||||||
static func setUp(binaryMessenger: FlutterBinaryMessenger, api: ExampleHostApi?) {
|
static func setUp(binaryMessenger: FlutterBinaryMessenger, api: ExampleHostApi?) {
|
||||||
let getHostLanguageChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.pigeon_example_package.ExampleHostApi.getHostLanguage", binaryMessenger: binaryMessenger, codec: codec)
|
let getHostLanguageChannel = FlutterBasicMessageChannel(
|
||||||
|
name: "dev.flutter.pigeon.pigeon_example_package.ExampleHostApi.getHostLanguage",
|
||||||
|
binaryMessenger: binaryMessenger, codec: codec)
|
||||||
if let api = api {
|
if let api = api {
|
||||||
getHostLanguageChannel.setMessageHandler { _, reply in
|
getHostLanguageChannel.setMessageHandler { _, reply in
|
||||||
do {
|
do {
|
||||||
@ -143,7 +147,9 @@ class ExampleHostApiSetup {
|
|||||||
} else {
|
} else {
|
||||||
getHostLanguageChannel.setMessageHandler(nil)
|
getHostLanguageChannel.setMessageHandler(nil)
|
||||||
}
|
}
|
||||||
let addChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.pigeon_example_package.ExampleHostApi.add", binaryMessenger: binaryMessenger, codec: codec)
|
let addChannel = FlutterBasicMessageChannel(
|
||||||
|
name: "dev.flutter.pigeon.pigeon_example_package.ExampleHostApi.add",
|
||||||
|
binaryMessenger: binaryMessenger, codec: codec)
|
||||||
if let api = api {
|
if let api = api {
|
||||||
addChannel.setMessageHandler { message, reply in
|
addChannel.setMessageHandler { message, reply in
|
||||||
let args = message as! [Any?]
|
let args = message as! [Any?]
|
||||||
@ -159,7 +165,9 @@ class ExampleHostApiSetup {
|
|||||||
} else {
|
} else {
|
||||||
addChannel.setMessageHandler(nil)
|
addChannel.setMessageHandler(nil)
|
||||||
}
|
}
|
||||||
let sendMessageChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.pigeon_example_package.ExampleHostApi.sendMessage", binaryMessenger: binaryMessenger, codec: codec)
|
let sendMessageChannel = FlutterBasicMessageChannel(
|
||||||
|
name: "dev.flutter.pigeon.pigeon_example_package.ExampleHostApi.sendMessage",
|
||||||
|
binaryMessenger: binaryMessenger, codec: codec)
|
||||||
if let api = api {
|
if let api = api {
|
||||||
sendMessageChannel.setMessageHandler { message, reply in
|
sendMessageChannel.setMessageHandler { message, reply in
|
||||||
let args = message as! [Any?]
|
let args = message as! [Any?]
|
||||||
@ -180,15 +188,19 @@ class ExampleHostApiSetup {
|
|||||||
}
|
}
|
||||||
/// Generated protocol from Pigeon that represents Flutter messages that can be called from Swift.
|
/// Generated protocol from Pigeon that represents Flutter messages that can be called from Swift.
|
||||||
protocol MessageFlutterApiProtocol {
|
protocol MessageFlutterApiProtocol {
|
||||||
func flutterMethod(aString aStringArg: String?, completion: @escaping (Result<String, FlutterError>) -> Void)
|
func flutterMethod(
|
||||||
|
aString aStringArg: String?, completion: @escaping (Result<String, FlutterError>) -> Void)
|
||||||
}
|
}
|
||||||
class MessageFlutterApi: MessageFlutterApiProtocol {
|
class MessageFlutterApi: MessageFlutterApiProtocol {
|
||||||
private let binaryMessenger: FlutterBinaryMessenger
|
private let binaryMessenger: FlutterBinaryMessenger
|
||||||
init(binaryMessenger: FlutterBinaryMessenger) {
|
init(binaryMessenger: FlutterBinaryMessenger) {
|
||||||
self.binaryMessenger = binaryMessenger
|
self.binaryMessenger = binaryMessenger
|
||||||
}
|
}
|
||||||
func flutterMethod(aString aStringArg: String?, completion: @escaping (Result<String, FlutterError>) -> Void) {
|
func flutterMethod(
|
||||||
let channelName: String = "dev.flutter.pigeon.pigeon_example_package.MessageFlutterApi.flutterMethod"
|
aString aStringArg: String?, completion: @escaping (Result<String, FlutterError>) -> Void
|
||||||
|
) {
|
||||||
|
let channelName: String =
|
||||||
|
"dev.flutter.pigeon.pigeon_example_package.MessageFlutterApi.flutterMethod"
|
||||||
let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger)
|
let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger)
|
||||||
channel.sendMessage([aStringArg] as [Any?]) { response in
|
channel.sendMessage([aStringArg] as [Any?]) { response in
|
||||||
guard let listResponse = response as? [Any?] else {
|
guard let listResponse = response as? [Any?] else {
|
||||||
@ -201,7 +213,11 @@ class MessageFlutterApi: MessageFlutterApiProtocol {
|
|||||||
let details: String? = nilOrValue(listResponse[2])
|
let details: String? = nilOrValue(listResponse[2])
|
||||||
completion(.failure(FlutterError(code: code, message: message, details: details)))
|
completion(.failure(FlutterError(code: code, message: message, details: details)))
|
||||||
} else if listResponse[0] == nil {
|
} else if listResponse[0] == nil {
|
||||||
completion(.failure(FlutterError(code: "null-error", message: "Flutter api returned null value for non-null return value.", details: "")))
|
completion(
|
||||||
|
.failure(
|
||||||
|
FlutterError(
|
||||||
|
code: "null-error",
|
||||||
|
message: "Flutter api returned null value for non-null return value.", details: "")))
|
||||||
} else {
|
} else {
|
||||||
let result = listResponse[0] as! String
|
let result = listResponse[0] as! String
|
||||||
completion(.success(result))
|
completion(.success(result))
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -13,6 +13,7 @@ library;
|
|||||||
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
|
|
||||||
import 'shared/generation.dart';
|
import 'shared/generation.dart';
|
||||||
@ -35,21 +36,66 @@ void _validateTestCoverage(List<List<String>> shards) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _validateGeneratedTestFiles() async {
|
Future<void> _validateGeneratedTestFiles() async {
|
||||||
|
await _validateGeneratedFiles(
|
||||||
|
(String baseDir) => generateTestPigeons(baseDir: baseDir),
|
||||||
|
generationMessage: 'Generating test output',
|
||||||
|
incorrectFilesMessage:
|
||||||
|
'The following files are not updated, or not formatted correctly:',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _validateGeneratedExampleFiles() async {
|
||||||
|
await _validateGeneratedFiles(
|
||||||
|
(String _) => generateExamplePigeons(),
|
||||||
|
generationMessage: 'Generating example output',
|
||||||
|
incorrectFilesMessage:
|
||||||
|
'Either messages.dart and messages_test.dart have non-matching definitions or\n'
|
||||||
|
'the following files are not updated, or not formatted correctly:',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _validateGeneratedFiles(
|
||||||
|
Future<int> Function(String baseDirectory) generator, {
|
||||||
|
required String generationMessage,
|
||||||
|
required String incorrectFilesMessage,
|
||||||
|
}) async {
|
||||||
|
// Generated file validation is split by platform, both to avoid duplication
|
||||||
|
// of work, and to avoid issues if different CI configurations have different
|
||||||
|
// setups (e.g., different clang-format versions or no clang-format at all).
|
||||||
|
final Set<GeneratorLanguage> languagesToValidate;
|
||||||
|
if (Platform.isLinux) {
|
||||||
|
languagesToValidate = <GeneratorLanguage>{
|
||||||
|
GeneratorLanguage.cpp,
|
||||||
|
GeneratorLanguage.dart,
|
||||||
|
GeneratorLanguage.java,
|
||||||
|
GeneratorLanguage.kotlin,
|
||||||
|
GeneratorLanguage.objc,
|
||||||
|
};
|
||||||
|
} else if (Platform.isMacOS) {
|
||||||
|
languagesToValidate = <GeneratorLanguage>{
|
||||||
|
GeneratorLanguage.swift,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
final String baseDir = p.dirname(p.dirname(Platform.script.toFilePath()));
|
final String baseDir = p.dirname(p.dirname(Platform.script.toFilePath()));
|
||||||
final String repositoryRoot = p.dirname(p.dirname(baseDir));
|
final String repositoryRoot = p.dirname(p.dirname(baseDir));
|
||||||
final String relativePigeonPath = p.relative(baseDir, from: repositoryRoot);
|
final String relativePigeonPath = p.relative(baseDir, from: repositoryRoot);
|
||||||
|
|
||||||
print('Validating generated files:');
|
print('Validating generated files:');
|
||||||
print(' Generating test output...');
|
print(' $generationMessage...');
|
||||||
final int generateExitCode = await generateTestPigeons(baseDir: baseDir);
|
|
||||||
|
final int generateExitCode = await generateExamplePigeons();
|
||||||
|
|
||||||
if (generateExitCode != 0) {
|
if (generateExitCode != 0) {
|
||||||
print('Generation failed; see above for errors.');
|
print('Generation failed; see above for errors.');
|
||||||
exit(generateExitCode);
|
exit(generateExitCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
print(' Formatting output...');
|
print(' Formatting output...');
|
||||||
final int formatExitCode =
|
final int formatExitCode = await formatAllFiles(
|
||||||
await formatAllFiles(repositoryRoot: repositoryRoot);
|
repositoryRoot: repositoryRoot, languages: languagesToValidate);
|
||||||
if (formatExitCode != 0) {
|
if (formatExitCode != 0) {
|
||||||
print('Formatting failed; see above for errors.');
|
print('Formatting failed; see above for errors.');
|
||||||
exit(formatExitCode);
|
exit(formatExitCode);
|
||||||
@ -58,20 +104,26 @@ Future<void> _validateGeneratedTestFiles() async {
|
|||||||
print(' Checking for changes...');
|
print(' Checking for changes...');
|
||||||
final List<String> modifiedFiles = await _modifiedFiles(
|
final List<String> modifiedFiles = await _modifiedFiles(
|
||||||
repositoryRoot: repositoryRoot, relativePigeonPath: relativePigeonPath);
|
repositoryRoot: repositoryRoot, relativePigeonPath: relativePigeonPath);
|
||||||
|
final Set<String> extensions = languagesToValidate
|
||||||
|
.map((GeneratorLanguage lang) => _extensionsForLanguage(lang))
|
||||||
|
.flattened
|
||||||
|
.toSet();
|
||||||
|
final Iterable<String> filteredFiles = modifiedFiles.where((String path) =>
|
||||||
|
extensions.contains(p.extension(path).replaceFirst('.', '')));
|
||||||
|
|
||||||
if (modifiedFiles.isEmpty) {
|
if (filteredFiles.isEmpty) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
print('The following files are not updated, or not formatted correctly:');
|
print(incorrectFilesMessage);
|
||||||
modifiedFiles.map((String line) => ' $line').forEach(print);
|
filteredFiles.map((String line) => ' $line').forEach(print);
|
||||||
|
|
||||||
print('\nTo fix run "dart run tool/generate.dart --format" from the pigeon/ '
|
print('\nTo fix run "dart run tool/generate.dart --format" from the pigeon/ '
|
||||||
'directory, or apply the diff with the command below.\n');
|
'directory, or apply the diff with the command below.\n');
|
||||||
|
|
||||||
final ProcessResult diffResult = await Process.run(
|
final ProcessResult diffResult = await Process.run(
|
||||||
'git',
|
'git',
|
||||||
<String>['diff', relativePigeonPath],
|
<String>['diff', ...filteredFiles],
|
||||||
workingDirectory: repositoryRoot,
|
workingDirectory: repositoryRoot,
|
||||||
);
|
);
|
||||||
if (diffResult.exitCode != 0) {
|
if (diffResult.exitCode != 0) {
|
||||||
@ -84,58 +136,15 @@ Future<void> _validateGeneratedTestFiles() async {
|
|||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _validateGeneratedExampleFiles() async {
|
Set<String> _extensionsForLanguage(GeneratorLanguage language) {
|
||||||
final String baseDir = p.dirname(p.dirname(Platform.script.toFilePath()));
|
return switch (language) {
|
||||||
final String repositoryRoot = p.dirname(p.dirname(baseDir));
|
GeneratorLanguage.cpp => <String>{'cc', 'cpp', 'h'},
|
||||||
final String relativePigeonPath = p.relative(baseDir, from: repositoryRoot);
|
GeneratorLanguage.dart => <String>{'dart'},
|
||||||
|
GeneratorLanguage.java => <String>{'java'},
|
||||||
print('Validating generated files:');
|
GeneratorLanguage.kotlin => <String>{'kt'},
|
||||||
print(' Generating example output...');
|
GeneratorLanguage.swift => <String>{'swift'},
|
||||||
|
GeneratorLanguage.objc => <String>{'h', 'm', 'mm'},
|
||||||
final int generateExitCode = await generateExamplePigeons();
|
};
|
||||||
|
|
||||||
if (generateExitCode != 0) {
|
|
||||||
print('Generation failed; see above for errors.');
|
|
||||||
exit(generateExitCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
print(' Formatting output...');
|
|
||||||
final int formatExitCode =
|
|
||||||
await formatAllFiles(repositoryRoot: repositoryRoot);
|
|
||||||
if (formatExitCode != 0) {
|
|
||||||
print('Formatting failed; see above for errors.');
|
|
||||||
exit(formatExitCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
print(' Checking for changes...');
|
|
||||||
final List<String> modifiedFiles = await _modifiedFiles(
|
|
||||||
repositoryRoot: repositoryRoot, relativePigeonPath: relativePigeonPath);
|
|
||||||
|
|
||||||
if (modifiedFiles.isEmpty) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
print(
|
|
||||||
'Either messages.dart and messages_test.dart have non-matching definitions or');
|
|
||||||
print('the following files are not updated, or not formatted correctly:');
|
|
||||||
modifiedFiles.map((String line) => ' $line').forEach(print);
|
|
||||||
|
|
||||||
print('\nTo fix run "dart run tool/generate.dart --format" from the pigeon/ '
|
|
||||||
'directory, or apply the diff with the command below.\n');
|
|
||||||
|
|
||||||
final ProcessResult diffResult = await Process.run(
|
|
||||||
'git',
|
|
||||||
<String>['diff', relativePigeonPath],
|
|
||||||
workingDirectory: repositoryRoot,
|
|
||||||
);
|
|
||||||
if (diffResult.exitCode != 0) {
|
|
||||||
print('Unable to determine diff.');
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
print('patch -p1 <<DONE');
|
|
||||||
print(diffResult.stdout);
|
|
||||||
print('DONE');
|
|
||||||
exit(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<String>> _modifiedFiles(
|
Future<List<String>> _modifiedFiles(
|
||||||
@ -204,20 +213,13 @@ Future<void> main(List<String> args) async {
|
|||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Ensure that all generated files are up to date. This is run only on Linux
|
// Ensure that all generated files are up to date.
|
||||||
// both to avoid duplication of work, and to avoid issues if different CI
|
// Only run on master, since Dart format can change between versions.
|
||||||
// configurations have different setups (e.g., different clang-format versions
|
if (Platform.environment['CHANNEL'] == 'stable') {
|
||||||
// or no clang-format at all).
|
print('Skipping generated file validation on stable.');
|
||||||
if (Platform.isLinux) {
|
} else {
|
||||||
// Only run on master, since Dart format can change between versions.
|
await _validateGeneratedTestFiles();
|
||||||
// TODO(stuartmorgan): Make a more generic way to run this check only on
|
await _validateGeneratedExampleFiles();
|
||||||
// master; this currently won't work for anything but Cirrus.
|
|
||||||
if (Platform.environment['CHANNEL'] == 'stable') {
|
|
||||||
print('Skipping generated file validation on stable.');
|
|
||||||
} else {
|
|
||||||
await _validateGeneratedTestFiles();
|
|
||||||
await _validateGeneratedExampleFiles();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<String> testsToRun;
|
final List<String> testsToRun;
|
||||||
|
@ -10,8 +10,9 @@ import 'package:pigeon/pigeon.dart';
|
|||||||
|
|
||||||
import 'process_utils.dart';
|
import 'process_utils.dart';
|
||||||
|
|
||||||
enum GeneratorLanguages {
|
enum GeneratorLanguage {
|
||||||
cpp,
|
cpp,
|
||||||
|
dart,
|
||||||
java,
|
java,
|
||||||
kotlin,
|
kotlin,
|
||||||
objc,
|
objc,
|
||||||
@ -20,8 +21,8 @@ enum GeneratorLanguages {
|
|||||||
|
|
||||||
// A map of pigeons/ files to the languages that they can't yet be generated
|
// A map of pigeons/ files to the languages that they can't yet be generated
|
||||||
// for due to limitations of that generator.
|
// for due to limitations of that generator.
|
||||||
const Map<String, Set<GeneratorLanguages>> _unsupportedFiles =
|
const Map<String, Set<GeneratorLanguage>> _unsupportedFiles =
|
||||||
<String, Set<GeneratorLanguages>>{};
|
<String, Set<GeneratorLanguage>>{};
|
||||||
|
|
||||||
String _snakeToPascalCase(String snake) {
|
String _snakeToPascalCase(String snake) {
|
||||||
final List<String> parts = snake.split('_');
|
final List<String> parts = snake.split('_');
|
||||||
@ -77,28 +78,28 @@ Future<int> generateTestPigeons({required String baseDir}) async {
|
|||||||
|
|
||||||
for (final String input in inputs) {
|
for (final String input in inputs) {
|
||||||
final String pascalCaseName = _snakeToPascalCase(input);
|
final String pascalCaseName = _snakeToPascalCase(input);
|
||||||
final Set<GeneratorLanguages> skipLanguages =
|
final Set<GeneratorLanguage> skipLanguages =
|
||||||
_unsupportedFiles[input] ?? <GeneratorLanguages>{};
|
_unsupportedFiles[input] ?? <GeneratorLanguage>{};
|
||||||
|
|
||||||
// Generate the default language test plugin output.
|
// Generate the default language test plugin output.
|
||||||
int generateCode = await runPigeon(
|
int generateCode = await runPigeon(
|
||||||
input: './pigeons/$input.dart',
|
input: './pigeons/$input.dart',
|
||||||
dartOut: '$sharedDartOutputBase/lib/src/generated/$input.gen.dart',
|
dartOut: '$sharedDartOutputBase/lib/src/generated/$input.gen.dart',
|
||||||
// Android
|
// Android
|
||||||
kotlinOut: skipLanguages.contains(GeneratorLanguages.kotlin)
|
kotlinOut: skipLanguages.contains(GeneratorLanguage.kotlin)
|
||||||
? null
|
? null
|
||||||
: '$outputBase/android/src/main/kotlin/com/example/test_plugin/$pascalCaseName.gen.kt',
|
: '$outputBase/android/src/main/kotlin/com/example/test_plugin/$pascalCaseName.gen.kt',
|
||||||
kotlinPackage: 'com.example.test_plugin',
|
kotlinPackage: 'com.example.test_plugin',
|
||||||
kotlinErrorClassName: '${pascalCaseName}Error',
|
kotlinErrorClassName: '${pascalCaseName}Error',
|
||||||
// iOS
|
// iOS
|
||||||
swiftOut: skipLanguages.contains(GeneratorLanguages.swift)
|
swiftOut: skipLanguages.contains(GeneratorLanguage.swift)
|
||||||
? null
|
? null
|
||||||
: '$outputBase/ios/Classes/$pascalCaseName.gen.swift',
|
: '$outputBase/ios/Classes/$pascalCaseName.gen.swift',
|
||||||
// Windows
|
// Windows
|
||||||
cppHeaderOut: skipLanguages.contains(GeneratorLanguages.cpp)
|
cppHeaderOut: skipLanguages.contains(GeneratorLanguage.cpp)
|
||||||
? null
|
? null
|
||||||
: '$outputBase/windows/pigeon/$input.gen.h',
|
: '$outputBase/windows/pigeon/$input.gen.h',
|
||||||
cppSourceOut: skipLanguages.contains(GeneratorLanguages.cpp)
|
cppSourceOut: skipLanguages.contains(GeneratorLanguage.cpp)
|
||||||
? null
|
? null
|
||||||
: '$outputBase/windows/pigeon/$input.gen.cpp',
|
: '$outputBase/windows/pigeon/$input.gen.cpp',
|
||||||
cppNamespace: '${input}_pigeontest',
|
cppNamespace: '${input}_pigeontest',
|
||||||
@ -114,7 +115,7 @@ Future<int> generateTestPigeons({required String baseDir}) async {
|
|||||||
// single invocation.
|
// single invocation.
|
||||||
generateCode = await runPigeon(
|
generateCode = await runPigeon(
|
||||||
input: './pigeons/$input.dart',
|
input: './pigeons/$input.dart',
|
||||||
swiftOut: skipLanguages.contains(GeneratorLanguages.swift)
|
swiftOut: skipLanguages.contains(GeneratorLanguage.swift)
|
||||||
? null
|
? null
|
||||||
: '$outputBase/macos/Classes/$pascalCaseName.gen.swift',
|
: '$outputBase/macos/Classes/$pascalCaseName.gen.swift',
|
||||||
suppressVersion: true,
|
suppressVersion: true,
|
||||||
@ -130,16 +131,16 @@ Future<int> generateTestPigeons({required String baseDir}) async {
|
|||||||
// Android
|
// Android
|
||||||
// This doesn't use the '.gen' suffix since Java has strict file naming
|
// This doesn't use the '.gen' suffix since Java has strict file naming
|
||||||
// rules.
|
// rules.
|
||||||
javaOut: skipLanguages.contains(GeneratorLanguages.java)
|
javaOut: skipLanguages.contains(GeneratorLanguage.java)
|
||||||
? null
|
? null
|
||||||
: '$alternateOutputBase/android/src/main/java/com/example/'
|
: '$alternateOutputBase/android/src/main/java/com/example/'
|
||||||
'alternate_language_test_plugin/${_javaFilenameForName(input)}.java',
|
'alternate_language_test_plugin/${_javaFilenameForName(input)}.java',
|
||||||
javaPackage: 'com.example.alternate_language_test_plugin',
|
javaPackage: 'com.example.alternate_language_test_plugin',
|
||||||
// iOS
|
// iOS
|
||||||
objcHeaderOut: skipLanguages.contains(GeneratorLanguages.objc)
|
objcHeaderOut: skipLanguages.contains(GeneratorLanguage.objc)
|
||||||
? null
|
? null
|
||||||
: '$alternateOutputBase/ios/Classes/$pascalCaseName.gen.h',
|
: '$alternateOutputBase/ios/Classes/$pascalCaseName.gen.h',
|
||||||
objcSourceOut: skipLanguages.contains(GeneratorLanguages.objc)
|
objcSourceOut: skipLanguages.contains(GeneratorLanguage.objc)
|
||||||
? null
|
? null
|
||||||
: '$alternateOutputBase/ios/Classes/$pascalCaseName.gen.m',
|
: '$alternateOutputBase/ios/Classes/$pascalCaseName.gen.m',
|
||||||
suppressVersion: true,
|
suppressVersion: true,
|
||||||
@ -154,10 +155,10 @@ Future<int> generateTestPigeons({required String baseDir}) async {
|
|||||||
// single invocation.
|
// single invocation.
|
||||||
generateCode = await runPigeon(
|
generateCode = await runPigeon(
|
||||||
input: './pigeons/$input.dart',
|
input: './pigeons/$input.dart',
|
||||||
objcHeaderOut: skipLanguages.contains(GeneratorLanguages.objc)
|
objcHeaderOut: skipLanguages.contains(GeneratorLanguage.objc)
|
||||||
? null
|
? null
|
||||||
: '$alternateOutputBase/macos/Classes/$pascalCaseName.gen.h',
|
: '$alternateOutputBase/macos/Classes/$pascalCaseName.gen.h',
|
||||||
objcSourceOut: skipLanguages.contains(GeneratorLanguages.objc)
|
objcSourceOut: skipLanguages.contains(GeneratorLanguage.objc)
|
||||||
? null
|
? null
|
||||||
: '$alternateOutputBase/macos/Classes/$pascalCaseName.gen.m',
|
: '$alternateOutputBase/macos/Classes/$pascalCaseName.gen.m',
|
||||||
suppressVersion: true,
|
suppressVersion: true,
|
||||||
@ -234,7 +235,17 @@ Future<int> runPigeon({
|
|||||||
/// This is intended for formatting generated output, but since there's no
|
/// This is intended for formatting generated output, but since there's no
|
||||||
/// way to filter to specific files in with the repo tooling it runs over the
|
/// way to filter to specific files in with the repo tooling it runs over the
|
||||||
/// entire package.
|
/// entire package.
|
||||||
Future<int> formatAllFiles({required String repositoryRoot}) {
|
Future<int> formatAllFiles({
|
||||||
|
required String repositoryRoot,
|
||||||
|
Set<GeneratorLanguage> languages = const <GeneratorLanguage>{
|
||||||
|
GeneratorLanguage.cpp,
|
||||||
|
GeneratorLanguage.dart,
|
||||||
|
GeneratorLanguage.java,
|
||||||
|
GeneratorLanguage.kotlin,
|
||||||
|
GeneratorLanguage.objc,
|
||||||
|
GeneratorLanguage.swift,
|
||||||
|
},
|
||||||
|
}) {
|
||||||
final String dartCommand = Platform.isWindows ? 'dart.exe' : 'dart';
|
final String dartCommand = Platform.isWindows ? 'dart.exe' : 'dart';
|
||||||
return runProcess(
|
return runProcess(
|
||||||
dartCommand,
|
dartCommand,
|
||||||
@ -242,8 +253,28 @@ Future<int> formatAllFiles({required String repositoryRoot}) {
|
|||||||
'run',
|
'run',
|
||||||
'script/tool/bin/flutter_plugin_tools.dart',
|
'script/tool/bin/flutter_plugin_tools.dart',
|
||||||
'format',
|
'format',
|
||||||
'--no-swift',
|
|
||||||
'--packages=pigeon',
|
'--packages=pigeon',
|
||||||
|
if (languages.contains(GeneratorLanguage.cpp) ||
|
||||||
|
languages.contains(GeneratorLanguage.objc))
|
||||||
|
'--clang-format'
|
||||||
|
else
|
||||||
|
'--no-clang-format',
|
||||||
|
if (languages.contains(GeneratorLanguage.java))
|
||||||
|
'--java'
|
||||||
|
else
|
||||||
|
'--no-java',
|
||||||
|
if (languages.contains(GeneratorLanguage.dart))
|
||||||
|
'--dart'
|
||||||
|
else
|
||||||
|
'--no-dart',
|
||||||
|
if (languages.contains(GeneratorLanguage.kotlin))
|
||||||
|
'--kotlin'
|
||||||
|
else
|
||||||
|
'--no-kotlin',
|
||||||
|
if (languages.contains(GeneratorLanguage.swift))
|
||||||
|
'--swift'
|
||||||
|
else
|
||||||
|
'--no-swift',
|
||||||
],
|
],
|
||||||
workingDirectory: repositoryRoot,
|
workingDirectory: repositoryRoot,
|
||||||
streamOutput: false,
|
streamOutput: false,
|
||||||
|
@ -5,12 +5,13 @@
|
|||||||
// See also: https://pub.dev/packages/pigeon
|
// See also: https://pub.dev/packages/pigeon
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
import Flutter
|
import Flutter
|
||||||
#elseif os(macOS)
|
#elseif os(macOS)
|
||||||
import FlutterMacOS
|
import FlutterMacOS
|
||||||
#else
|
#else
|
||||||
#error("Unsupported platform.")
|
#error("Unsupported platform.")
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
extension FlutterError: Error {}
|
extension FlutterError: Error {}
|
||||||
@ -28,13 +29,13 @@ private func wrapError(_ error: Any) -> [Any?] {
|
|||||||
return [
|
return [
|
||||||
flutterError.code,
|
flutterError.code,
|
||||||
flutterError.message,
|
flutterError.message,
|
||||||
flutterError.details
|
flutterError.details,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
return [
|
return [
|
||||||
"\(error)",
|
"\(error)",
|
||||||
"\(type(of: error))",
|
"\(type(of: error))",
|
||||||
"Stacktrace: \(Thread.callStackSymbols)"
|
"Stacktrace: \(Thread.callStackSymbols)",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,10 +77,10 @@ struct ShortcutItemMessage {
|
|||||||
private class IOSQuickActionsApiCodecReader: FlutterStandardReader {
|
private class IOSQuickActionsApiCodecReader: FlutterStandardReader {
|
||||||
override func readValue(ofType type: UInt8) -> Any? {
|
override func readValue(ofType type: UInt8) -> Any? {
|
||||||
switch type {
|
switch type {
|
||||||
case 128:
|
case 128:
|
||||||
return ShortcutItemMessage.fromList(self.readValue() as! [Any?])
|
return ShortcutItemMessage.fromList(self.readValue() as! [Any?])
|
||||||
default:
|
default:
|
||||||
return super.readValue(ofType: type)
|
return super.readValue(ofType: type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -124,7 +125,9 @@ class IOSQuickActionsApiSetup {
|
|||||||
/// Sets up an instance of `IOSQuickActionsApi` to handle messages through the `binaryMessenger`.
|
/// Sets up an instance of `IOSQuickActionsApi` to handle messages through the `binaryMessenger`.
|
||||||
static func setUp(binaryMessenger: FlutterBinaryMessenger, api: IOSQuickActionsApi?) {
|
static func setUp(binaryMessenger: FlutterBinaryMessenger, api: IOSQuickActionsApi?) {
|
||||||
/// Sets the dynamic shortcuts for the app.
|
/// Sets the dynamic shortcuts for the app.
|
||||||
let setShortcutItemsChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.quick_actions_ios.IOSQuickActionsApi.setShortcutItems", binaryMessenger: binaryMessenger, codec: codec)
|
let setShortcutItemsChannel = FlutterBasicMessageChannel(
|
||||||
|
name: "dev.flutter.pigeon.quick_actions_ios.IOSQuickActionsApi.setShortcutItems",
|
||||||
|
binaryMessenger: binaryMessenger, codec: codec)
|
||||||
if let api = api {
|
if let api = api {
|
||||||
setShortcutItemsChannel.setMessageHandler { message, reply in
|
setShortcutItemsChannel.setMessageHandler { message, reply in
|
||||||
let args = message as! [Any?]
|
let args = message as! [Any?]
|
||||||
@ -140,7 +143,9 @@ class IOSQuickActionsApiSetup {
|
|||||||
setShortcutItemsChannel.setMessageHandler(nil)
|
setShortcutItemsChannel.setMessageHandler(nil)
|
||||||
}
|
}
|
||||||
/// Removes all dynamic shortcuts.
|
/// Removes all dynamic shortcuts.
|
||||||
let clearShortcutItemsChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.quick_actions_ios.IOSQuickActionsApi.clearShortcutItems", binaryMessenger: binaryMessenger, codec: codec)
|
let clearShortcutItemsChannel = FlutterBasicMessageChannel(
|
||||||
|
name: "dev.flutter.pigeon.quick_actions_ios.IOSQuickActionsApi.clearShortcutItems",
|
||||||
|
binaryMessenger: binaryMessenger, codec: codec)
|
||||||
if let api = api {
|
if let api = api {
|
||||||
clearShortcutItemsChannel.setMessageHandler { _, reply in
|
clearShortcutItemsChannel.setMessageHandler { _, reply in
|
||||||
do {
|
do {
|
||||||
@ -158,16 +163,21 @@ class IOSQuickActionsApiSetup {
|
|||||||
/// Generated protocol from Pigeon that represents Flutter messages that can be called from Swift.
|
/// Generated protocol from Pigeon that represents Flutter messages that can be called from Swift.
|
||||||
protocol IOSQuickActionsFlutterApiProtocol {
|
protocol IOSQuickActionsFlutterApiProtocol {
|
||||||
/// Sends a string representing a shortcut from the native platform to the app.
|
/// Sends a string representing a shortcut from the native platform to the app.
|
||||||
func launchAction(action actionArg: String, completion: @escaping (Result<Void, FlutterError>) -> Void)
|
func launchAction(
|
||||||
|
action actionArg: String, completion: @escaping (Result<Void, FlutterError>) -> Void)
|
||||||
}
|
}
|
||||||
class IOSQuickActionsFlutterApi: IOSQuickActionsFlutterApiProtocol {
|
class IOSQuickActionsFlutterApi: IOSQuickActionsFlutterApiProtocol {
|
||||||
private let binaryMessenger: FlutterBinaryMessenger
|
private let binaryMessenger: FlutterBinaryMessenger
|
||||||
init(binaryMessenger: FlutterBinaryMessenger){
|
init(binaryMessenger: FlutterBinaryMessenger) {
|
||||||
self.binaryMessenger = binaryMessenger
|
self.binaryMessenger = binaryMessenger
|
||||||
}
|
}
|
||||||
/// Sends a string representing a shortcut from the native platform to the app.
|
/// Sends a string representing a shortcut from the native platform to the app.
|
||||||
func launchAction(action actionArg: String, completion: @escaping (Result<Void, FlutterError>) -> Void) {
|
func launchAction(
|
||||||
let channel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.quick_actions_ios.IOSQuickActionsFlutterApi.launchAction", binaryMessenger: binaryMessenger)
|
action actionArg: String, completion: @escaping (Result<Void, FlutterError>) -> Void
|
||||||
|
) {
|
||||||
|
let channel = FlutterBasicMessageChannel(
|
||||||
|
name: "dev.flutter.pigeon.quick_actions_ios.IOSQuickActionsFlutterApi.launchAction",
|
||||||
|
binaryMessenger: binaryMessenger)
|
||||||
channel.sendMessage([actionArg] as [Any?]) { _ in
|
channel.sendMessage([actionArg] as [Any?]) { _ in
|
||||||
completion(.success(Void()))
|
completion(.success(Void()))
|
||||||
}
|
}
|
||||||
|
@ -179,10 +179,8 @@ class FormatCommand extends PackageCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _formatAndLintSwift(Iterable<String> files) async {
|
Future<void> _formatAndLintSwift(Iterable<String> files) async {
|
||||||
// TODO(jmagman): Remove generated file filter when pigeon Swift generation matches swift-format.
|
final Iterable<String> swiftFiles =
|
||||||
// https://github.com/flutter/flutter/issues/141799
|
_getPathsWithExtensions(files, <String>{'.swift'});
|
||||||
final Iterable<String> swiftFiles = _filterGeneratedFiles(
|
|
||||||
_getPathsWithExtensions(files, <String>{'.swift'}));
|
|
||||||
if (swiftFiles.isNotEmpty) {
|
if (swiftFiles.isNotEmpty) {
|
||||||
final String swiftFormat = await _findValidSwiftFormat();
|
final String swiftFormat = await _findValidSwiftFormat();
|
||||||
print('Formatting .swift files...');
|
print('Formatting .swift files...');
|
||||||
@ -359,13 +357,6 @@ class FormatCommand extends PackageCommand {
|
|||||||
(String filePath) => extensions.contains(path.extension(filePath)));
|
(String filePath) => extensions.contains(path.extension(filePath)));
|
||||||
}
|
}
|
||||||
|
|
||||||
Iterable<String> _filterGeneratedFiles(Iterable<String> files) {
|
|
||||||
return files.where((String filePath) {
|
|
||||||
final String basename = path.basename(filePath);
|
|
||||||
return !basename.contains('.gen.') && !basename.contains('.g.');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String> _getJavaFormatterPath() async {
|
Future<String> _getJavaFormatterPath() async {
|
||||||
final String javaFormatterPath = path.join(
|
final String javaFormatterPath = path.join(
|
||||||
path.dirname(path.fromUri(platform.script)),
|
path.dirname(path.fromUri(platform.script)),
|
||||||
|
@ -573,22 +573,6 @@ void main() {
|
|||||||
]));
|
]));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('skips generated Swift files', () async {
|
|
||||||
const List<String> files = <String>[
|
|
||||||
'macos/foo.gen.swift',
|
|
||||||
'macos/foo.g.swift',
|
|
||||||
];
|
|
||||||
createFakePlugin(
|
|
||||||
'a_plugin',
|
|
||||||
packagesDir,
|
|
||||||
extraFiles: files,
|
|
||||||
);
|
|
||||||
|
|
||||||
await runCapturingPrint(runner, <String>['format', '--swift']);
|
|
||||||
|
|
||||||
expect(processRunner.recordedCalls, orderedEquals(<ProcessCall>[]));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('skips Swift if --no-swift flag is provided', () async {
|
test('skips Swift if --no-swift flag is provided', () async {
|
||||||
const List<String> files = <String>[
|
const List<String> files = <String>[
|
||||||
'macos/foo.swift',
|
'macos/foo.swift',
|
||||||
|
Reference in New Issue
Block a user